mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-05 16:00:01 +01:00
Using wasm to accelerate PixelManipulation.js (#1093)
* Add wasm code * First working model * Add PixelManipulation web assembly code to browser and node * Tests corrected for modules * Corrected test script * Add wasm bechmarks * Update Readme * Applies toggling functionality and refactored PixelManipulation code * Added documentation and corrected wasm toggling * change noise reduction module to use wasm code * Corrected formatting and removed extra comments * Add default wasm option and made README changes * Fixed negative test timings * combined benchmarks file * Update benchmark.js * Removed copies of wasm file and corrected test format * Update package.json Co-Authored-By: Jeffrey Warren <jeff@unterbahn.com> * Added wasm file and removed redundant code * Removed earlier benchmarks * move test/core/sequencer/benchmark.js to its own test command, not passing to tape-spec * Solves memory leaks and blank lines * Solves memory leaks and blank lines * Added handler for node code * Modify test script * Modify test script * Correct doc and removed pace fuctionality
This commit is contained in:
committed by
Jeffrey Warren
parent
c3af98ea93
commit
30659d4656
@@ -280,7 +280,6 @@ module.exports = function ModuleName(options,UI) {
|
||||
|
||||
The `progressObj` parameter of `draw()` is not consumed unless a custom progress bar needs to be drawn, for which this default spinner should be stopped with `progressObj.stop()` and image-sequencer is informed about the custom progress bar with `progressObj.overrideFlag = true;` following which this object can be overriden with custom progress object.
|
||||
|
||||
The pixelManipulation API can draw progress bars internally using the `pace` npm package. The option is disabled by default but can be enabled by passing `ui: true` in the options for pixelManipulation. The recommended way is to use `ui: options.step.ui`. This will only show the progress if the ui is set to true by the user, while creating the sequencer object.
|
||||
|
||||
### Module example
|
||||
|
||||
|
||||
21
README.md
21
README.md
@@ -578,3 +578,24 @@ sequencer2.run();
|
||||
This method returns an object which defines the name and inputs of the modules. If a module name (hyphenated) is passed in the method, then only the details of that module are returned.
|
||||
|
||||
The `notify` function takes two parameters `msg` and `id`, former being the message to be displayed on console (in case of CLI and node ) and a HTML component(in browser). The id is optional and is useful for HTML interface to give appropriate IDs.
|
||||
|
||||
## Using WebAssembly for heavy pixel processing
|
||||
|
||||
Any module which uses the `changePixel` function gets WebAssembly acceleration (`wasm`). Both node and browser code use WebAssembly and the only code which falls back to non-`wasm` code is the [browserified unit tests](https://github.com/publiclab/image-sequencer/blob/main/test/core/sequencer/benchmark.js).
|
||||
|
||||
The main advantage we get using `wasm` is blazing fast speed attained in processing pixels for many modules that is very clear from [checking module benchmarks](https://travis-ci.org/publiclab/image-sequencer/jobs/544415673#L1931).
|
||||
|
||||
|
||||
The only limitation is that browser and node code for `wasm` had to be written separately, and switched between. This is because in browser we use `fetch` to retrieve the compiled `wasm` program while in node we use the `fs` module, each of which cannot be used in the other's environment.
|
||||
|
||||
|
||||
`wasm` mode is enabled by default. If you need to force this mode to be on or off, you can use the `useWasm` option when initializing ImageSequencer:
|
||||
|
||||
```js
|
||||
let sequencer = ImageSequencer({useWasm:true}) // for wasm mode or simply
|
||||
|
||||
let sequencer = ImageSequencer() // also for wasm mode i.e. default mode
|
||||
|
||||
let sequencer = ImageSequencer({useWasm:false}) //for non-wasm mode
|
||||
|
||||
```
|
||||
BIN
dist/manipulation.wasm
vendored
Normal file
BIN
dist/manipulation.wasm
vendored
Normal file
Binary file not shown.
@@ -81,7 +81,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
var inputDesc = isInput ? mapHtmlTypes(inputs[paramName]) : {};
|
||||
if (!isInput) {
|
||||
html += '<span class="output"></span>';
|
||||
}
|
||||
}
|
||||
else if (inputDesc.type.toLowerCase() == 'select') {
|
||||
|
||||
html += '<select class="form-control target" name="' + paramName + '">';
|
||||
@@ -94,7 +94,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
let paramVal = step.options[paramName] || inputDesc.default;
|
||||
|
||||
if (inputDesc.id == 'color-picker') { // separate input field for color-picker
|
||||
html +=
|
||||
html +=
|
||||
'<div id="color-picker" class="input-group colorpicker-component">' +
|
||||
'<input class="form-control target" type="' +
|
||||
inputDesc.type +
|
||||
@@ -122,7 +122,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
'"max="' +
|
||||
inputDesc.max +
|
||||
'"step="' +
|
||||
(inputDesc.step ? inputDesc.step : 1)+ '">' + '<span>' + paramVal + '</span>';
|
||||
(inputDesc.step ? inputDesc.step : 1) + '">' + '<span>' + paramVal + '</span>';
|
||||
|
||||
}
|
||||
else html += '">';
|
||||
|
||||
4123
package-lock.json
generated
4123
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
|
||||
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/*.js | tap-spec; browserify test/core/sequencer/image-sequencer.js test/core/sequencer/chain.js test/core/sequencer/meta-modules.js test/core/sequencer/replace.js test/core/sequencer/import-export.js test/core/sequencer/run.js test/core/sequencer/dynamic-imports.js test/core/util/*.js test/core/sequencer/benchmark.js | tape-run --render=\"tap-spec\"",
|
||||
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/*.js | tap-spec; node test/core/sequencer/benchmark.js; browserify test/core/sequencer/meta-modules.js test/core/sequencer/image-sequencer.js test/core/sequencer/chain.js test/core/sequencer/replace.js test/core/sequencer/import-export.js test/core/sequencer/run.js test/core/sequencer/dynamic-imports.js test/core/util/*.js | tape-run --render=\"tap-spec\"",
|
||||
"test-ui": "node node_modules/jasmine/bin/jasmine test/spec/*.js",
|
||||
"setup": "npm i && npm i -g grunt grunt-cli && grunt build",
|
||||
"start": "grunt serve"
|
||||
|
||||
@@ -8,7 +8,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
options = options || {};
|
||||
options.inBrowser = options.inBrowser === undefined ? isBrowser : options.inBrowser;
|
||||
options.sequencerCounter = 0;
|
||||
|
||||
function objTypeOf(object) {
|
||||
return Object.prototype.toString.call(object).split(' ')[1].slice(0, -1);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ function InsertStep(ref, index, name, o) {
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.inBrowser = ref.options.inBrowser;
|
||||
|
||||
o.useWasm = (ref.options.useWasm === false) ? false : true;
|
||||
if (index == -1) index = ref.steps.length;
|
||||
|
||||
o.step = {
|
||||
|
||||
@@ -28,7 +28,6 @@ module.exports = function AddQR(options, UI) {
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
@@ -37,7 +36,8 @@ module.exports = function AddQR(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ module.exports = function Average(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ module.exports = function Blur(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ module.exports = function Brightness(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ module.exports = function canvasResize(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ module.exports = function Channel(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ module.exports = function ColorTemperature(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ module.exports = function Colormap(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ module.exports = function Contrast(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ module.exports = function Convolution(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ module.exports = function DoNothing(options, UI) {
|
||||
ui: options.step.ui,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ module.exports = function Dither(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -32,7 +32,8 @@ module.exports = function DrawRectangle(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ module.exports = function Dynamic(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ module.exports = function edgeDetect(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm: options.useWasm
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,8 @@ module.exports = function Exposure(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ module.exports = function FlipImage(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ module.exports = function Gamma(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ module.exports = function GridOverlay(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm: options.useWasm
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ module.exports = function Channel(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = function ImportImageModule(options, UI) {
|
||||
step.metadata.input = input;
|
||||
// options.format = require('../../util/GetFormat')(options.imageUrl);
|
||||
|
||||
var helper = ImageSequencer({ inBrowser: options.inBrowser, ui: false });
|
||||
var helper = ImageSequencer({ inBrowser: options.inBrowser, ui: false, useWasm: options.useWasm });
|
||||
helper.loadImages(options.imageUrl, () => {
|
||||
step.output = helper.steps[0].output;
|
||||
callback();
|
||||
|
||||
@@ -30,7 +30,8 @@ function Invert(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ module.exports = function Ndvi(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: modifiedCallback
|
||||
callback: modifiedCallback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ module.exports = function NoiseReduction(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -70,7 +70,8 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
format: baseStepOutput.format,
|
||||
image: baseStepImage,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ module.exports = function PaintBucket(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ module.exports = function ReplaceColor(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ module.exports = exports = function(pixels, options){
|
||||
color = color.substring(color.indexOf('(') + 1, color.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
|
||||
var replaceColor = options.replaceColor || 'rgb(0,0,255)';
|
||||
replaceColor = replaceColor.substring(replaceColor.indexOf('(') + 1 , replaceColor.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
replaceColor = replaceColor.substring(replaceColor.indexOf('(') + 1, replaceColor.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
|
||||
var replaceMethod = options.replaceMethod || 'greyscale';
|
||||
color = color.split(',');
|
||||
|
||||
@@ -60,7 +60,8 @@ module.exports = function Resize(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ module.exports = function Rotate(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ module.exports = function Saturation(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ module.exports = function TextOverlay(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ module.exports = function ImageThreshold(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -39,7 +39,8 @@ module.exports = function Tint(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ module.exports = function Balance(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/*
|
||||
* General purpose per-pixel manipulation
|
||||
* accepting a changePixel() method to remix a pixel's channels
|
||||
*/
|
||||
* General purpose per-pixel manipulation
|
||||
* accepting a changePixel() method to remix a pixel's channels
|
||||
*/
|
||||
module.exports = function PixelManipulation(image, options) {
|
||||
|
||||
// To handle the case where pixelmanipulation is called on the input object itself
|
||||
// like input.pixelManipulation(options)
|
||||
if (arguments.length <= 1) {
|
||||
@@ -16,7 +15,7 @@ module.exports = function PixelManipulation(image, options) {
|
||||
const getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
getPixels(image.src, function(err, pixels) {
|
||||
getPixels(image.src, function (err, pixels) {
|
||||
if (err) {
|
||||
console.log('Bad image path', image);
|
||||
return;
|
||||
@@ -32,69 +31,117 @@ module.exports = function PixelManipulation(image, options) {
|
||||
// TODO: this could possibly be more efficient; see
|
||||
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181
|
||||
|
||||
if (!options.inBrowser && !process.env.TEST && options.ui) {
|
||||
try {
|
||||
var pace = require('pace')(pixels.shape[0] * pixels.shape[1]);
|
||||
} catch (e) {
|
||||
options.inBrowser = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.preProcess) pixels = options.preProcess(pixels); // Allow for preprocessing
|
||||
|
||||
function extraOperation() {
|
||||
var res;
|
||||
if (options.extraManipulation) res = options.extraManipulation(pixels, generateOutput);
|
||||
// there may be a more efficient means to encode an image object,
|
||||
// but node modules and their documentation are essentially arcane on this point
|
||||
function generateOutput() {
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
|
||||
var r = savePixels(pixels, options.format, {
|
||||
quality: 100
|
||||
});
|
||||
|
||||
r.on('data', function (chunk) {
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
r.on('end', function () {
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
if (options.output)
|
||||
options.output(options.image, datauri, options.format);
|
||||
if (options.callback) options.callback();
|
||||
});
|
||||
}
|
||||
if (res) {
|
||||
pixels = res;
|
||||
generateOutput();
|
||||
} else if (!options.extraManipulation) generateOutput();
|
||||
}
|
||||
|
||||
if (!options.changePixel) extraOperation();
|
||||
|
||||
if (options.changePixel) {
|
||||
|
||||
/* Allows for Flexibility
|
||||
if per pixel manipulation is not required */
|
||||
|
||||
for (var x = 0; x < pixels.shape[0]; x++) {
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
let pixel = options.changePixel(
|
||||
pixels.get(x, y, 0),
|
||||
pixels.get(x, y, 1),
|
||||
pixels.get(x, y, 2),
|
||||
pixels.get(x, y, 3),
|
||||
x,
|
||||
y
|
||||
);
|
||||
const imports = {
|
||||
env: {
|
||||
consoleLog: console.log,
|
||||
perform: function (x, y) {
|
||||
let pixel = options.changePixel(
|
||||
pixels.get(x, y, 0),
|
||||
pixels.get(x, y, 1),
|
||||
pixels.get(x, y, 2),
|
||||
pixels.get(x, y, 3),
|
||||
x,
|
||||
y
|
||||
);
|
||||
|
||||
pixels.set(x, y, 0, pixel[0]);
|
||||
pixels.set(x, y, 1, pixel[1]);
|
||||
pixels.set(x, y, 2, pixel[2]);
|
||||
pixels.set(x, y, 3, pixel[3]);
|
||||
pixels.set(x, y, 0, pixel[0]);
|
||||
pixels.set(x, y, 1, pixel[1]);
|
||||
pixels.set(x, y, 2, pixel[2]);
|
||||
pixels.set(x, y, 3, pixel[3]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.inBrowser && !process.env.TEST && options.ui) pace.op();
|
||||
function perPixelManipulation() { // pure JavaScript code
|
||||
for (var x = 0; x < pixels.shape[0]; x++) {
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
imports.env.perform(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// perform any extra operations on the entire array:
|
||||
var res;
|
||||
if (options.extraManipulation) res = options.extraManipulation(pixels, generateOutput);
|
||||
// there may be a more efficient means to encode an image object,
|
||||
// but node modules and their documentation are essentially arcane on this point
|
||||
function generateOutput() {
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
|
||||
var r = savePixels(pixels, options.format, { quality: 100 });
|
||||
const inBrowser = (options.inBrowser) ? 1 : 0;
|
||||
const test = (process.env.TEST) ? 1 : 0;
|
||||
if (options.useWasm) {
|
||||
if (options.inBrowser) {
|
||||
|
||||
r.on('data', function(chunk) {
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
r.on('end', function() {
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
if (options.output)
|
||||
options.output(options.image, datauri, options.format);
|
||||
if (options.callback) options.callback();
|
||||
});
|
||||
fetch('../../../dist/manipulation.wasm').then(response =>
|
||||
response.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, imports)
|
||||
).then(results => {
|
||||
results.instance.exports.manipulatePixel(pixels.shape[0], pixels.shape[1], inBrowser, test);
|
||||
extraOperation();
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
console.log('WebAssembly acceleration errored; falling back to JavaScript in PixelManipulation');
|
||||
perPixelManipulation();
|
||||
extraOperation();
|
||||
});
|
||||
} else {
|
||||
try{
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const wasmPath = path.join(__dirname, '../../../', 'dist', 'manipulation.wasm');
|
||||
const buf = fs.readFileSync(wasmPath);
|
||||
WebAssembly.instantiate(buf, imports).then(results => {
|
||||
results.instance.exports.manipulatePixel(pixels.shape[0], pixels.shape[1], inBrowser, test);
|
||||
extraOperation();
|
||||
});
|
||||
}
|
||||
catch(err){
|
||||
console.log(err);
|
||||
console.log('WebAssembly acceleration errored; falling back to JavaScript in PixelManipulation');
|
||||
perPixelManipulation();
|
||||
extraOperation();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
perPixelManipulation();
|
||||
extraOperation();
|
||||
}
|
||||
}
|
||||
if (res) {
|
||||
pixels = res;
|
||||
generateOutput();
|
||||
}
|
||||
else if (!options.extraManipulation) generateOutput();
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
var fs = require('fs');
|
||||
var test = require('tape');
|
||||
var DataURItoBuffer = require('data-uri-to-buffer');
|
||||
require('events').EventEmitter.prototype.setMaxListeners(100);
|
||||
|
||||
ImageSequencer = require('../../../src/ImageSequencer.js');
|
||||
|
||||
@@ -11,10 +12,23 @@ var image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xA
|
||||
var imageName = 'image1';
|
||||
|
||||
test('benchmark all modules', function(t) {
|
||||
var sequencerDefault = ImageSequencer({ ui: false, inBrowser: false, useWasm:false });
|
||||
|
||||
console.log('############ Benchmarks ############');
|
||||
runBenchmarks(sequencerDefault, t);
|
||||
console.log('####################################');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false, inBrowser: false });
|
||||
});
|
||||
|
||||
test('benchmark all modules with WebAssembly', function(t) {
|
||||
var sequencerWebAssembly = ImageSequencer({ ui: false, inBrowser: false, useWasm: true });
|
||||
|
||||
console.log('############ Benchmarks with WebAssembly ############');
|
||||
runBenchmarks(sequencerWebAssembly, t);
|
||||
console.log('####################################');
|
||||
});
|
||||
|
||||
function runBenchmarks(sequencer, t) {
|
||||
var mods = Object.keys(sequencer.modules);
|
||||
|
||||
sequencer.loadImages(image);
|
||||
@@ -29,11 +43,11 @@ test('benchmark all modules', function(t) {
|
||||
var end = Date.now();
|
||||
console.log('Module ' + mods[0] + ' ran in: ' + (end - global.start) + ' milliseconds');
|
||||
mods.splice(0, 1);
|
||||
if (mods.length > 1) { //Last one is test module, we need not benchmark it
|
||||
if (mods.length > 1) { // Last one is test module, we need not benchmark it
|
||||
sequencer.steps[global.idx].output.src = image;
|
||||
global.idx++;
|
||||
if (mods[0] === 'import-image' || (!!sequencer.modulesInfo(mods[0]).requires && sequencer.modulesInfo(mods[0]).requires.includes('webgl'))) {
|
||||
/* Not currently working */
|
||||
/* Not currently working for this module, which is for importing a new image */
|
||||
console.log('Bypassing import-image');
|
||||
mods.splice(0, 1);
|
||||
}
|
||||
@@ -41,8 +55,7 @@ test('benchmark all modules', function(t) {
|
||||
global.start = Date.now();
|
||||
sequencer.run({ index: global.idx }, cb);
|
||||
} else {
|
||||
console.log('####################################');
|
||||
t.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var test = require('tape');
|
||||
require('../../../src/ImageSequencer.js');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var sequencer = ImageSequencer({ ui: false});
|
||||
var red = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z';
|
||||
|
||||
test('Dynamically add a test Module', function(t) {
|
||||
|
||||
@@ -36,7 +36,6 @@ module.exports = (moduleName, options, benchmark, input) => {
|
||||
|
||||
test(`${moduleName} module works correctly`, t => {
|
||||
sequencer.run({mode: 'test'}, () => {
|
||||
|
||||
let result = sequencer.steps[1].output.src;
|
||||
|
||||
base64Img.imgSync(result, target, 'result');
|
||||
|
||||
Reference in New Issue
Block a user