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:
Slytherin
2019-06-21 20:24:56 +05:30
committed by Jeffrey Warren
parent c3af98ea93
commit 30659d4656
48 changed files with 2243 additions and 2203 deletions

View File

@@ -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 `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 ### Module example

View File

@@ -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. 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. 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

Binary file not shown.

View File

@@ -81,7 +81,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
var inputDesc = isInput ? mapHtmlTypes(inputs[paramName]) : {}; var inputDesc = isInput ? mapHtmlTypes(inputs[paramName]) : {};
if (!isInput) { if (!isInput) {
html += '<span class="output"></span>'; html += '<span class="output"></span>';
} }
else if (inputDesc.type.toLowerCase() == 'select') { else if (inputDesc.type.toLowerCase() == 'select') {
html += '<select class="form-control target" name="' + paramName + '">'; html += '<select class="form-control target" name="' + paramName + '">';
@@ -94,7 +94,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
let paramVal = step.options[paramName] || inputDesc.default; let paramVal = step.options[paramName] || inputDesc.default;
if (inputDesc.id == 'color-picker') { // separate input field for color-picker if (inputDesc.id == 'color-picker') { // separate input field for color-picker
html += html +=
'<div id="color-picker" class="input-group colorpicker-component">' + '<div id="color-picker" class="input-group colorpicker-component">' +
'<input class="form-control target" type="' + '<input class="form-control target" type="' +
inputDesc.type + inputDesc.type +
@@ -122,7 +122,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
'"max="' + '"max="' +
inputDesc.max + inputDesc.max +
'"step="' + '"step="' +
(inputDesc.step ? inputDesc.step : 1)+ '">' + '<span>' + paramVal + '</span>'; (inputDesc.step ? inputDesc.step : 1) + '">' + '<span>' + paramVal + '</span>';
} }
else html += '">'; else html += '">';

4123
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"main": "src/ImageSequencer.js", "main": "src/ImageSequencer.js",
"scripts": { "scripts": {
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert", "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", "test-ui": "node node_modules/jasmine/bin/jasmine test/spec/*.js",
"setup": "npm i && npm i -g grunt grunt-cli && grunt build", "setup": "npm i && npm i -g grunt grunt-cli && grunt build",
"start": "grunt serve" "start": "grunt serve"

View File

@@ -8,7 +8,6 @@ ImageSequencer = function ImageSequencer(options) {
options = options || {}; options = options || {};
options.inBrowser = options.inBrowser === undefined ? isBrowser : options.inBrowser; options.inBrowser = options.inBrowser === undefined ? isBrowser : options.inBrowser;
options.sequencerCounter = 0; options.sequencerCounter = 0;
function objTypeOf(object) { function objTypeOf(object) {
return Object.prototype.toString.call(object).split(' ')[1].slice(0, -1); return Object.prototype.toString.call(object).split(' ')[1].slice(0, -1);
} }

View File

@@ -25,7 +25,7 @@ function InsertStep(ref, index, name, o) {
o.selector = o_.selector || 'ismod-' + name; o.selector = o_.selector || 'ismod-' + name;
o.container = o_.container || ref.options.selector; o.container = o_.container || ref.options.selector;
o.inBrowser = ref.options.inBrowser; o.inBrowser = ref.options.inBrowser;
o.useWasm = (ref.options.useWasm === false) ? false : true;
if (index == -1) index = ref.steps.length; if (index == -1) index = ref.steps.length;
o.step = { o.step = {

View File

@@ -28,7 +28,6 @@ module.exports = function AddQR(options, UI) {
function output(image, datauri, mimetype) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype }; step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
@@ -37,7 +36,8 @@ module.exports = function AddQR(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
}); });

View File

@@ -62,7 +62,8 @@ module.exports = function Average(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -62,7 +62,8 @@ module.exports = function Dynamic(options, UI, util) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
}); });
} }

View File

@@ -33,7 +33,8 @@ module.exports = function Blur(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -45,7 +45,8 @@ module.exports = function Brightness(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -50,7 +50,8 @@ module.exports = function canvasResize(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -36,7 +36,8 @@ module.exports = function Channel(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -68,7 +68,8 @@ module.exports = function ColorTemperature(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -29,7 +29,8 @@ module.exports = function Colormap(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -33,7 +33,8 @@ module.exports = function Contrast(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -31,7 +31,8 @@ module.exports = function Convolution(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -40,7 +40,8 @@ module.exports = function DoNothing(options, UI) {
ui: options.step.ui, ui: options.step.ui,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -27,7 +27,8 @@ module.exports = function Dither(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }
return { return {

View File

@@ -32,7 +32,8 @@ module.exports = function DrawRectangle(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -86,7 +86,8 @@ module.exports = function Dynamic(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -50,7 +50,8 @@ module.exports = function edgeDetect(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm: options.useWasm
}); });
}); });
}); });

View File

@@ -38,7 +38,8 @@ module.exports = function Exposure(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -37,7 +37,8 @@ module.exports = function FlipImage(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
}); });

View File

@@ -35,7 +35,8 @@ module.exports = function Gamma(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -29,7 +29,8 @@ module.exports = function GridOverlay(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm: options.useWasm
}); });

View File

@@ -80,7 +80,8 @@ module.exports = function Channel(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -32,7 +32,7 @@ module.exports = function ImportImageModule(options, UI) {
step.metadata.input = input; step.metadata.input = input;
// options.format = require('../../util/GetFormat')(options.imageUrl); // 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, () => { helper.loadImages(options.imageUrl, () => {
step.output = helper.steps[0].output; step.output = helper.steps[0].output;
callback(); callback();

View File

@@ -30,7 +30,8 @@ function Invert(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -46,7 +46,8 @@ module.exports = function Ndvi(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: modifiedCallback callback: modifiedCallback,
useWasm:options.useWasm
}); });
} }

View File

@@ -27,7 +27,8 @@ module.exports = function NoiseReduction(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }
return { return {

View File

@@ -70,7 +70,8 @@ module.exports = function Dynamic(options, UI, util) {
format: baseStepOutput.format, format: baseStepOutput.format,
image: baseStepImage, image: baseStepImage,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
}); });
} }

View File

@@ -28,7 +28,8 @@ module.exports = function PaintBucket(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -32,7 +32,8 @@ module.exports = function ReplaceColor(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -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(_,_,_,_) color = color.substring(color.indexOf('(') + 1, color.length - 1); // extract only the values from rgba(_,_,_,_)
var replaceColor = options.replaceColor || 'rgb(0,0,255)'; 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'; var replaceMethod = options.replaceMethod || 'greyscale';
color = color.split(','); color = color.split(',');

View File

@@ -60,7 +60,8 @@ module.exports = function Resize(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -51,7 +51,8 @@ module.exports = function Rotate(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -45,7 +45,8 @@ module.exports = function Saturation(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -36,7 +36,8 @@ module.exports = function TextOverlay(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -33,7 +33,8 @@ module.exports = function ImageThreshold(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }
return { return {

View File

@@ -39,7 +39,8 @@ module.exports = function Tint(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -45,7 +45,8 @@ module.exports = function Balance(options, UI) {
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: callback callback: callback,
useWasm:options.useWasm
}); });
} }

View File

@@ -1,9 +1,8 @@
/* /*
* General purpose per-pixel manipulation * General purpose per-pixel manipulation
* accepting a changePixel() method to remix a pixel's channels * accepting a changePixel() method to remix a pixel's channels
*/ */
module.exports = function PixelManipulation(image, options) { module.exports = function PixelManipulation(image, options) {
// To handle the case where pixelmanipulation is called on the input object itself // To handle the case where pixelmanipulation is called on the input object itself
// like input.pixelManipulation(options) // like input.pixelManipulation(options)
if (arguments.length <= 1) { if (arguments.length <= 1) {
@@ -16,7 +15,7 @@ module.exports = function PixelManipulation(image, options) {
const getPixels = require('get-pixels'), const getPixels = require('get-pixels'),
savePixels = require('save-pixels'); savePixels = require('save-pixels');
getPixels(image.src, function(err, pixels) { getPixels(image.src, function (err, pixels) {
if (err) { if (err) {
console.log('Bad image path', image); console.log('Bad image path', image);
return; return;
@@ -32,69 +31,117 @@ module.exports = function PixelManipulation(image, options) {
// TODO: this could possibly be more efficient; see // TODO: this could possibly be more efficient; see
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181 // 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 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) { if (options.changePixel) {
/* Allows for Flexibility /* Allows for Flexibility
if per pixel manipulation is not required */ if per pixel manipulation is not required */
for (var x = 0; x < pixels.shape[0]; x++) { const imports = {
for (var y = 0; y < pixels.shape[1]; y++) { env: {
let pixel = options.changePixel( consoleLog: console.log,
pixels.get(x, y, 0), perform: function (x, y) {
pixels.get(x, y, 1), let pixel = options.changePixel(
pixels.get(x, y, 2), pixels.get(x, y, 0),
pixels.get(x, y, 3), pixels.get(x, y, 1),
x, pixels.get(x, y, 2),
y pixels.get(x, y, 3),
); x,
y
);
pixels.set(x, y, 0, pixel[0]); pixels.set(x, y, 0, pixel[0]);
pixels.set(x, y, 1, pixel[1]); pixels.set(x, y, 1, pixel[1]);
pixels.set(x, y, 2, pixel[2]); pixels.set(x, y, 2, pixel[2]);
pixels.set(x, y, 3, pixel[3]); 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) { fetch('../../../dist/manipulation.wasm').then(response =>
totalLength += chunk.length; response.arrayBuffer()
chunks.push(chunk); ).then(bytes =>
}); WebAssembly.instantiate(bytes, imports)
).then(results => {
r.on('end', function() { results.instance.exports.manipulatePixel(pixels.shape[0], pixels.shape[1], inBrowser, test);
var data = Buffer.concat(chunks, totalLength).toString('base64'); extraOperation();
var datauri = 'data:image/' + options.format + ';base64,' + data; }).catch(err => {
if (options.output) console.log(err);
options.output(options.image, datauri, options.format); console.log('WebAssembly acceleration errored; falling back to JavaScript in PixelManipulation');
if (options.callback) options.callback(); 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();
}); });
}; };

View File

@@ -3,6 +3,7 @@
var fs = require('fs'); var fs = require('fs');
var test = require('tape'); var test = require('tape');
var DataURItoBuffer = require('data-uri-to-buffer'); var DataURItoBuffer = require('data-uri-to-buffer');
require('events').EventEmitter.prototype.setMaxListeners(100);
ImageSequencer = require('../../../src/ImageSequencer.js'); ImageSequencer = require('../../../src/ImageSequencer.js');
@@ -11,10 +12,23 @@ var image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xA
var imageName = 'image1'; var imageName = 'image1';
test('benchmark all modules', function(t) { test('benchmark all modules', function(t) {
var sequencerDefault = ImageSequencer({ ui: false, inBrowser: false, useWasm:false });
console.log('############ Benchmarks ############'); 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); var mods = Object.keys(sequencer.modules);
sequencer.loadImages(image); sequencer.loadImages(image);
@@ -29,11 +43,11 @@ test('benchmark all modules', function(t) {
var end = Date.now(); var end = Date.now();
console.log('Module ' + mods[0] + ' ran in: ' + (end - global.start) + ' milliseconds'); console.log('Module ' + mods[0] + ' ran in: ' + (end - global.start) + ' milliseconds');
mods.splice(0, 1); 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; sequencer.steps[global.idx].output.src = image;
global.idx++; global.idx++;
if (mods[0] === 'import-image' || (!!sequencer.modulesInfo(mods[0]).requires && sequencer.modulesInfo(mods[0]).requires.includes('webgl'))) { 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'); console.log('Bypassing import-image');
mods.splice(0, 1); mods.splice(0, 1);
} }
@@ -41,8 +55,7 @@ test('benchmark all modules', function(t) {
global.start = Date.now(); global.start = Date.now();
sequencer.run({ index: global.idx }, cb); sequencer.run({ index: global.idx }, cb);
} else { } else {
console.log('####################################');
t.end(); t.end();
} }
} }
}); }

View File

@@ -1,7 +1,7 @@
var test = require('tape'); var test = require('tape');
require('../../../src/ImageSequencer.js'); 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'; 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) { test('Dynamically add a test Module', function(t) {

View File

@@ -36,7 +36,6 @@ module.exports = (moduleName, options, benchmark, input) => {
test(`${moduleName} module works correctly`, t => { test(`${moduleName} module works correctly`, t => {
sequencer.run({mode: 'test'}, () => { sequencer.run({mode: 'test'}, () => {
let result = sequencer.steps[1].output.src; let result = sequencer.steps[1].output.src;
base64Img.imgSync(result, target, 'result'); base64Img.imgSync(result, target, 'result');