mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-09 01:39:59 +01:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cce65141d2 | ||
|
|
b707592588 | ||
|
|
0638a1c68c | ||
|
|
6106e5ee0c | ||
|
|
d4eff45664 | ||
|
|
5a3b94d4ee | ||
|
|
a238fc65e1 | ||
|
|
e15e981d0b | ||
|
|
43e6253095 | ||
|
|
6be996692a | ||
|
|
5b4e904636 | ||
|
|
5ab018fb84 | ||
|
|
10e47656a3 | ||
|
|
56feb4c546 | ||
|
|
73a9cd2961 | ||
|
|
42d49112ef | ||
|
|
0c0147354e | ||
|
|
510cf3421a | ||
|
|
8d6b82d988 | ||
|
|
5132612dc2 | ||
|
|
f4315159a9 | ||
|
|
de20254637 | ||
|
|
10c6ad5865 | ||
|
|
a0f7e6c766 | ||
|
|
2b07ecdb36 | ||
|
|
37122d4c28 | ||
|
|
871453b857 | ||
|
|
ac183205f0 | ||
|
|
be810fb9c4 | ||
|
|
8d3ca0887f | ||
|
|
1de72d7325 | ||
|
|
58a4798674 | ||
|
|
f8006da07e | ||
|
|
09c4f250a2 | ||
|
|
bd490d2515 | ||
|
|
ca79924d11 | ||
|
|
630eb773de | ||
|
|
6ce78f87f4 | ||
|
|
991e9bb29c | ||
|
|
cbbfbff4bc | ||
|
|
768e2ce95d | ||
|
|
51524f747a | ||
|
|
b16b45afa8 | ||
|
|
39418da187 |
143
CONTRIBUTING.md
143
CONTRIBUTING.md
@@ -35,23 +35,18 @@ Any module must follow this basic format:
|
||||
```js
|
||||
module.exports = function ModuleName(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
// Module requirements have been simplified in version 3, see https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md#contributing-modules
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
UI.onDraw(options.step); // tell the UI to "draw"
|
||||
|
||||
var output = function(input){
|
||||
/* do something with the input */
|
||||
return input;
|
||||
}
|
||||
|
||||
this.output = output(input); // run the output and assign it to this.output
|
||||
options.step.output = output.src;
|
||||
callback();
|
||||
UI.onComplete(options.step); // tell UI we are done
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -63,6 +58,57 @@ module.exports = function ModuleName(options,UI) {
|
||||
}
|
||||
```
|
||||
|
||||
Image Sequencer modules are designed to be run either in the browser or in a Node.js environment. For dynamically loaded modules, that means that any uses of `require()` to include an external library must be compiled using a system like `browserify` or `webpack` to ensure browser compatibility. An example of this can be found here:
|
||||
|
||||
https://github.com/tech4gt/image-sequencer
|
||||
|
||||
If you wish to offer a module without browser-compatibility, please indicate this in the returned `info` object as:
|
||||
|
||||
module.exports = [
|
||||
ModuleName,
|
||||
{ only: ['node'] }
|
||||
];
|
||||
|
||||
If you believe that full cross-compatibility is possible, but need help, please open an issue on your module's issue tracker requesting assistance (and potentially link to it from an inline comment or the module description).
|
||||
|
||||
Any Independent Module Must follow this basic format
|
||||
```js
|
||||
function ModuleName(options,UI) {
|
||||
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
var output = function(input){
|
||||
/* do something with the input */
|
||||
return input;
|
||||
}
|
||||
|
||||
this.output = output(input); // run the output and assign it to this.output
|
||||
callback();
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = [ModuleName,{
|
||||
"name": "ModuleName",
|
||||
"description": "",
|
||||
"inputs": {
|
||||
// inputs here
|
||||
}
|
||||
/* Info can be defined here or imported from a json file */
|
||||
// require('./info.json') This only works in node
|
||||
// In a module compiled with browserify or webpack, a require() can be used to
|
||||
// load a standard info.json file.
|
||||
];
|
||||
```
|
||||
|
||||
|
||||
### options
|
||||
|
||||
@@ -84,9 +130,18 @@ The `draw` method should accept an `input` parameter, which will be an object of
|
||||
```js
|
||||
input = {
|
||||
src: "datauri of an image here",
|
||||
format: "jpeg/png/etc"
|
||||
format: "jpeg/png/etc",
|
||||
// utility functions
|
||||
getPixels: "function to get Image pixels. Wrapper around https://npmjs.com/get-pixels",
|
||||
savePixels: "function to save Image pixels. Wrapper around https://npmjs.com/save-pixels",
|
||||
lodash: "wrapper around lodash library, https://github.com/lodash",
|
||||
dataUriToBuffer: "wrapper around https://www.npmjs.com/package/data-uri-to-buffer",
|
||||
pixelManipulation: "general purpose pixel manipulation API, see https://github.com/publiclab/image-sequencer/blob/master/src/modules/_nomodule/PixelManipulation.js"
|
||||
}
|
||||
```
|
||||
For example usage for pixelManipulation see https://github.com/publiclab/image-sequencer/blob/master/src/modules/Invert/Module.js
|
||||
|
||||
**The module is included in the browser inside a script tag and since the code runs directly in the browser if any other module is required apart from the apis available on the input object, it should be either bundled with the module code and imported in es6 format or the module code must be browserified before distribution for browser**
|
||||
|
||||
The draw method is run every time the step is `run` using `sequencer.run()`.
|
||||
So any calculations must go **into** the `draw()` method's definition.
|
||||
@@ -171,14 +226,11 @@ The default "loading spinner" can be optionally overriden with a custom progress
|
||||
```js
|
||||
module.exports = function ModuleName(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
/* If you wish to supply your own progress bar you need to override progressObj */
|
||||
|
||||
progressObj.stop() // Stop the current progress spinner
|
||||
|
||||
progressObj.overrideFlag = true; // Tell image sequencer that you will supply your own progressBar
|
||||
@@ -186,17 +238,13 @@ module.exports = function ModuleName(options,UI) {
|
||||
/* Override the object and give your own progress Bar */
|
||||
progressObj = /* Your own progress Object */
|
||||
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var output = function(input){
|
||||
/* do something with the input */
|
||||
return input;
|
||||
};
|
||||
|
||||
this.output = output();
|
||||
options.step.output = output.src;
|
||||
callback();
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -217,3 +265,70 @@ See existing module `green-channel` for an example: https://github.com/publiclab
|
||||
The `green-channel` module is included into the core modules here: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
|
||||
|
||||
For help integrating, please open an issue.
|
||||
|
||||
## Meta Module
|
||||
|
||||
IMAGE SEQUENCER supports "meta modules" -- modules made of other modules. The syntax and structure of these meta modules is very similar to standard modules. Sequencer can also genarate meta modules dynamically with the function `createMetaModule` which can be called in the following ways
|
||||
|
||||
```js
|
||||
// stepsString is a stringified sequence
|
||||
sequencer.createMetaModule(stepsString,info)
|
||||
|
||||
/* stepsArray is the array of objects in this format
|
||||
* [
|
||||
* {name: "moduleName",options: {}},
|
||||
* {name: "moduleName",options: {}}
|
||||
* ]
|
||||
*/
|
||||
sequencer.createMetaModule(stepsArray,info)
|
||||
```
|
||||
|
||||
A Meta module can also be contributed like a normal module with an info and a Module.js. A basic Meta module shall follow the following format
|
||||
|
||||
|
||||
```js
|
||||
// Module.js
|
||||
module.exports = function metaModuleFun(){
|
||||
this.expandSteps([
|
||||
{ 'name': 'module-name', 'options': {} },
|
||||
{ 'name': 'module-name', options: {} }
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// Info
|
||||
{
|
||||
"name": "meta-moduleName",
|
||||
"description": "",
|
||||
"length": //Integer representing number of steps in the metaModule
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
//index.js
|
||||
module.exports = [
|
||||
require('./Module.js'),
|
||||
require('./info.json')
|
||||
]
|
||||
```
|
||||
|
||||
All of the above can also be combined together to form a single file module.
|
||||
|
||||
```js
|
||||
// MetaModule.js
|
||||
module.export = [
|
||||
function (){
|
||||
this.expandSteps([
|
||||
{ 'name': 'module-name', 'options': {} },
|
||||
{ 'name': 'module-name', options: {} }
|
||||
]);
|
||||
},
|
||||
{
|
||||
"name": "meta-moduleName",
|
||||
"description": "",
|
||||
"length": //Integer representing number of steps in the metaModule
|
||||
}
|
||||
]
|
||||
```
|
||||
The length is absolutely required for a meta-module, since sequencer is optimized to re-run minimum number of steps when a step is added in the UI which is 1 in the case of normal modules, if the added step is a meta-module the length of the sequence governs the number of steps to re-run.
|
||||
48
Gruntfile.js
48
Gruntfile.js
@@ -1,50 +1,50 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks("grunt-browserify");
|
||||
grunt.loadNpmTasks("grunt-contrib-uglify-es");
|
||||
grunt.loadNpmTasks("grunt-browser-sync");
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
|
||||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
|
||||
require("matchdep")
|
||||
.filterDev("grunt-*")
|
||||
.forEach(grunt.loadNpmTasks);
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
|
||||
watch: {
|
||||
options: {
|
||||
livereload: true
|
||||
},
|
||||
source: {
|
||||
files: [
|
||||
'src/*.js',
|
||||
'src/*/*.js',
|
||||
'Gruntfile.js'
|
||||
],
|
||||
tasks: [ 'build:js' ]
|
||||
files: ["src/**/*", "Gruntfile.js"],
|
||||
tasks: ["build:js"]
|
||||
}
|
||||
},
|
||||
|
||||
browserify: {
|
||||
dist: {
|
||||
src: ['src/ImageSequencer.js'],
|
||||
dest: 'dist/image-sequencer.js'
|
||||
src: ["src/ImageSequencer.js"],
|
||||
dest: "dist/image-sequencer.js"
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
dist: {
|
||||
src: ['./dist/image-sequencer.js'],
|
||||
dest: './dist/image-sequencer.min.js'
|
||||
src: ["./dist/image-sequencer.js"],
|
||||
dest: "./dist/image-sequencer.min.js"
|
||||
}
|
||||
},
|
||||
browserSync: {
|
||||
dev: {
|
||||
options: {
|
||||
watchTask: true,
|
||||
server: "./"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Default (development): Watch files and build on change. */
|
||||
grunt.registerTask('default', ['watch']);
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'browserify:dist',
|
||||
'uglify:dist'
|
||||
]);
|
||||
|
||||
grunt.registerTask("default", ["watch"]);
|
||||
grunt.registerTask("build", ["browserify:dist", "uglify:dist"]);
|
||||
grunt.registerTask("serve", ["browserify:dist", "uglify:dist", "browserSync", "watch"]);
|
||||
};
|
||||
|
||||
92
README.md
92
README.md
@@ -45,7 +45,7 @@ A diagram of this running 5 steps on a single sample image may help explain how
|
||||
|
||||
## Installation
|
||||
|
||||
This library works in the browser, in Node, and on the commandline (CLI), which we think is great.
|
||||
This library works in the browser, in Node, and on the commandline (CLI), which we think is great. You can start a local environement to test the UI with `npm start`
|
||||
|
||||
### Browser
|
||||
|
||||
@@ -104,6 +104,8 @@ Image Sequencer also provides a CLI for applying operations to local files. The
|
||||
-b | --basic | Basic mode only outputs the final image
|
||||
-o | --output [PATH] | Directory where output will be stored. (optional)
|
||||
-c | --config {object} | Options for the step. (optional)
|
||||
--save-sequence [string] | Name space separated with Stringified sequence to save
|
||||
--install-module [string] | Module name space seaprated npm package name
|
||||
|
||||
The basic format for using the CLI is as follows:
|
||||
|
||||
@@ -123,13 +125,22 @@ But for this, double quotes must wrap the space-separated steps.
|
||||
|
||||
Options for the steps can be passed in one line as json in the details option like
|
||||
```
|
||||
$ ./index.js -i [PATH] -s "brightness" -d '{"brightness":50}'
|
||||
$ ./index.js -i [PATH] -s "brightness" -c '{"brightness":50}'
|
||||
|
||||
```
|
||||
Or the values can be given through terminal prompt like
|
||||
|
||||
<img width="1436" alt="screen shot 2018-02-14 at 5 18 50 pm" src="https://user-images.githubusercontent.com/25617855/36202790-3c6e8204-11ab-11e8-9e17-7f3387ab0158.png">
|
||||
|
||||
`save-sequence` option can be used to save a sequence and the associated options for later usage. You should provide a string which contains a name of the sequence space separated from the sequence of steps which constitute it.
|
||||
```shell
|
||||
sequencer --save-sequence "invert-colormap invert(),colormap()"
|
||||
```
|
||||
|
||||
`install-module` option can be used to install new modules from npm. You can register this module in your sequencer with a custom name space sepated with the npm package name. Below is an example for the `image-sequencer-invert` module.
|
||||
```shell
|
||||
sequencer --install-module "invert image-sequencer-invert"
|
||||
```
|
||||
|
||||
The CLI is also chainable with other commands using `&&`
|
||||
|
||||
@@ -204,13 +215,35 @@ modules.
|
||||
sequencer.run();
|
||||
```
|
||||
|
||||
Additionally, an optional callback can be passed to this method.
|
||||
Sequencer can be run with a custom config object
|
||||
|
||||
```js
|
||||
sequencer.run(function(out){
|
||||
// The config object enables custom progress bars in node environment and
|
||||
// ability to run the sequencer from a particular index(of the steps array)
|
||||
|
||||
sequencer.run(config);
|
||||
|
||||
```
|
||||
|
||||
The config object can have the following keys
|
||||
|
||||
```js
|
||||
config: {
|
||||
progressObj: , //A custom object to handle progress bar
|
||||
index: //Index to run the sequencer from (defaults to 0)
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, an optional callback function can be passed to this method.
|
||||
|
||||
```js
|
||||
sequencer.run(function callback(out){
|
||||
// this gets called back.
|
||||
// "out" is the DataURL of the final image.
|
||||
});
|
||||
sequencer.run(config,function callback(out){
|
||||
// the callback is supported with all types of invocations
|
||||
});
|
||||
```
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
@@ -255,8 +288,18 @@ sequencer.insertSteps(index,module_name,optional_options);
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
### Importing an independent module
|
||||
|
||||
The `loadNewModule` method can be used to import a new module inside sequencer. Modules can be downloaded via npm, yarn or cdn and are imported with a custom name. If you wish to load a new module at runtime, it will need to avoid using `require()` -- unless it is compiled with a system like browserify or webpack.
|
||||
|
||||
```js
|
||||
const module = require('sequencer-moduleName')
|
||||
sequencer.loadNewModule('moduleName',module);
|
||||
```
|
||||
|
||||
|
||||
## Method Chaining
|
||||
|
||||
Methods can be chained on the Image Sequencer:
|
||||
* loadImage()/loadImages() can only terminate a chain.
|
||||
* run() can not be in the middle of the chain.
|
||||
@@ -429,6 +472,47 @@ sequencer.insertSteps({
|
||||
```
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
## Saving Sequences
|
||||
|
||||
IMAGE SEQUENCER supports saving a sequence of modules and their associated settings in a simple string syntax. These sequences can be saved in the local storage inside the browser and inside a json file in node.js. sequences can be saved in node context using the CLI option
|
||||
|
||||
```shell
|
||||
--save-sequence "name stringified-sequence"
|
||||
```
|
||||
|
||||
In Node and the browser the following function can be used
|
||||
```js
|
||||
sequencer.saveSequence(name,sequenceString)
|
||||
```
|
||||
|
||||
The function `sequencer.loadModules()` reloads the modules and the saved sequences into `sequencer.modules` and `sequencer.sequences`
|
||||
|
||||
|
||||
## String syntax
|
||||
|
||||
Image sequencer supports stringifying a sequence which is appended to the url and hence can then be shared. An example below shows the string syntax for `channel` and `invert` module
|
||||
```
|
||||
channel{channel:green},invert{}
|
||||
```
|
||||
|
||||
Sequencer also supports use of `()` in place of `{}` for backwards compatibility with older links.(This syntax is deprecated and should be avoided as far as possible)
|
||||
```
|
||||
channel(channel:green),invert()
|
||||
```
|
||||
|
||||
Following are the core API functions that can be used to stringify and jsonify steps.
|
||||
```js
|
||||
sequencer.toString() //returns the stringified sequence of current steps
|
||||
sequencer.toJSON(str) // returns the JSON for the current sequence
|
||||
sequencer.stringToJSON(str) // returns the JSON for given stringified sequence
|
||||
sequencer.importString(str) //Imports the sequence of steps into sequencer
|
||||
sequencer.importJSON(obj) //Imports the given sequence of JSON steps into sequencer
|
||||
```
|
||||
|
||||
Image Sequencer can also generate a string for usage in the CLI for the current sequence of steps:
|
||||
```js
|
||||
sequencer.toCliString()
|
||||
```
|
||||
|
||||
## Creating a User Interface
|
||||
|
||||
|
||||
35000
dist/image-sequencer.js
vendored
Executable file → Normal file
35000
dist/image-sequencer.js
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
2
dist/image-sequencer.min.js
vendored
2
dist/image-sequencer.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -107,3 +107,7 @@ h1 {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
}
|
||||
#save-seq {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
225
examples/demo.js
225
examples/demo.js
@@ -1,208 +1,49 @@
|
||||
window.onload = function() {
|
||||
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
function refreshOptions() {
|
||||
// Load information of all modules (Name, Inputs, Outputs)
|
||||
var modulesInfo = sequencer.modulesInfo();
|
||||
|
||||
var addStepSelect = $("#addStep select");
|
||||
addStepSelect.html("");
|
||||
|
||||
// Add modules to the addStep dropdown
|
||||
for (var m in modulesInfo) {
|
||||
$('#addStep select').append(
|
||||
'<option value="'+m+'">'+modulesInfo[m].name+'</option>'
|
||||
addStepSelect.append(
|
||||
'<option value="' + m + '">' + modulesInfo[m].name + "</option>"
|
||||
);
|
||||
}
|
||||
|
||||
// Initial definitions
|
||||
var steps = document.querySelector('#steps');
|
||||
var parser = new DOMParser();
|
||||
var reader = new FileReader();
|
||||
|
||||
// Set the UI in sequencer. This Will generate HTML based on
|
||||
// Image Sequencer events :
|
||||
// onSetup : Called every time a step is added
|
||||
// onDraw : Called every time a step starts draw
|
||||
// onComplete : Called every time a step finishes drawing
|
||||
// onRemove : Called everytime a step is removed
|
||||
// The variable 'step' stores useful data like input and
|
||||
// output values, step information.
|
||||
// See documetation for more details.
|
||||
sequencer.setUI({
|
||||
|
||||
onSetup: function(step) {
|
||||
|
||||
if (step.options && step.options.description) step.description = step.options.description
|
||||
|
||||
step.ui = '\
|
||||
<div class="row step">\
|
||||
<div class="col-md-4 details">\
|
||||
<h3>'+step.name+'</h3>\
|
||||
<p><i>'+(step.description || '')+'</i></p>\
|
||||
</div>\
|
||||
<div class="col-md-8">\
|
||||
<div class="load" style="display:none;"><i class="fa fa-circle-o-notch fa-spin"></i></div>\
|
||||
<a><img alt="" class=“img-thumbnail” /></a>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn btn-default">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
</div>';
|
||||
|
||||
step.ui = parser.parseFromString(step.ui,'text/html');
|
||||
step.ui = step.ui.querySelector('div.row');
|
||||
step.linkElement = step.ui.querySelector('a');
|
||||
step.imgElement = step.ui.querySelector('a img');
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
var merged = Object.assign(inputs, outputs); // combine outputs w inputs
|
||||
for (var paramName in merged) {
|
||||
var isInput = inputs.hasOwnProperty(paramName);
|
||||
var html = "";
|
||||
var inputDesc = (isInput)?inputs[paramName]:{};
|
||||
if (!isInput) {
|
||||
html += "<span class=\"output\"></span>";
|
||||
} else if (inputDesc.type.toLowerCase() == "select") {
|
||||
html += "<select class=\"form-control\" name=\""+paramName+"\">";
|
||||
for (var option in inputDesc.values) {
|
||||
html += "<option>"+inputDesc.values[option]+"</option>";
|
||||
}
|
||||
html += "</select>";
|
||||
} else {
|
||||
html = "<input class=\"form-control\" type=\""+inputDesc.type+"\" name=\""+paramName+"\">";
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
div.className = "row";
|
||||
div.setAttribute('name', paramName);
|
||||
var description = inputs[paramName].desc || paramName;
|
||||
div.innerHTML = "<div class='det'>\
|
||||
<label for='" + paramName + "'>" + description + "</label>\
|
||||
"+html+"\
|
||||
</div>";
|
||||
step.ui.querySelector('div.details').appendChild(div);
|
||||
}
|
||||
$(step.ui.querySelector('div.details')).append("<p><button class='btn btn-default btn-save'>Save</button></p>");
|
||||
refreshOptions();
|
||||
|
||||
function saveOptions() {
|
||||
$(step.ui.querySelector('div.details')).find('input,select').each(function(i, input) {
|
||||
step.options[$(input).attr('name')] = input.value;
|
||||
// UI for each step:
|
||||
sequencer.setUI(DefaultHtmlStepUi(sequencer));
|
||||
|
||||
// UI for the overall demo:
|
||||
var ui = DefaultHtmlSequencerUi(sequencer);
|
||||
|
||||
sequencer.loadImage("images/tulips.png", ui.onLoad);
|
||||
|
||||
$("#addStep select").on("change", ui.selectNewStepUi);
|
||||
$("#addStep button").on("click", ui.addStepUi);
|
||||
$('body').on('click', 'button.remove', ui.removeStepUi);
|
||||
$('#save-seq').click(() => {
|
||||
sequencer.saveSequence(window.prompt("Please give a name to your sequence..."), sequencer.toString());
|
||||
sequencer.loadModules();
|
||||
refreshOptions();
|
||||
});
|
||||
sequencer.run();
|
||||
}
|
||||
|
||||
// on clicking Save in the details pane of the step
|
||||
$(step.ui.querySelector('div.details .btn-save')).click(saveOptions);
|
||||
}
|
||||
|
||||
if(step.name != "load-image")
|
||||
step.ui.querySelector('div.details').appendChild(
|
||||
parser.parseFromString(tools,'text/html')
|
||||
.querySelector('div')
|
||||
);
|
||||
|
||||
steps.appendChild(step.ui);
|
||||
},
|
||||
|
||||
onDraw: function(step) {
|
||||
$(step.ui.querySelector('.load')).show();
|
||||
$(step.ui.querySelector('img')).hide();
|
||||
},
|
||||
|
||||
onComplete: function(step) {
|
||||
$(step.ui.querySelector('.load')).hide();
|
||||
$(step.ui.querySelector('img')).show();
|
||||
|
||||
step.imgElement.src = step.output;
|
||||
step.linkElement.href = step.output;
|
||||
|
||||
function fileExtension(output) {
|
||||
return output.split('/')[1].split(';')[0];
|
||||
}
|
||||
|
||||
step.linkElement.download = step.name + "." + fileExtension(step.output);
|
||||
step.linkElement.target = "_blank";
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
for (var i in inputs) {
|
||||
if (step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "input") step.ui.querySelector('div[name="' + i + '"] input')
|
||||
.value = step.options[i];
|
||||
if (step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "select") step.ui.querySelector('div[name="' + i + '"] select')
|
||||
.value = step.options[i];
|
||||
}
|
||||
for (var i in outputs) {
|
||||
if (step[i] !== undefined) step.ui.querySelector('div[name="'+i+'"] input')
|
||||
.value = step[i];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onRemove: function(step) {
|
||||
step.ui.remove();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
sequencer.loadImage('images/tulips.png', function loadImageUI() {
|
||||
|
||||
// look up needed steps from Url Hash:
|
||||
var hash = getUrlHashParameter('steps');
|
||||
|
||||
if (hash) {
|
||||
var stepsFromHash = hash.split(',');
|
||||
stepsFromHash.forEach(function eachStep(step) {
|
||||
sequencer.addSteps(step);
|
||||
});
|
||||
sequencer.run();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$('#addStep select').on('change', selectNewStepUI);
|
||||
|
||||
function selectNewStepUI() {
|
||||
var m = $('#addStep select').val();
|
||||
$('#addStep .info').html(sequencer.modulesInfo(m).description);
|
||||
}
|
||||
|
||||
function addStepUI() {
|
||||
var options = {};
|
||||
var inputs = $('#options input, #options select');
|
||||
$.each(inputs, function() {
|
||||
options[this.name] = $(this).val();
|
||||
});
|
||||
if($('#addStep select').val() == "none") return;
|
||||
// add to URL hash too
|
||||
var hash = getUrlHashParameter('steps') || '';
|
||||
if (hash != '') hash += ',';
|
||||
setUrlHashParameter('steps', hash + $('#addStep select').val())
|
||||
sequencer.addSteps($('#addStep select').val(),options).run();
|
||||
}
|
||||
|
||||
$('#addStep button').on('click', addStepUI);
|
||||
|
||||
|
||||
function removeStepUI(){
|
||||
var index = $('button.remove').index(this) + 1;
|
||||
sequencer.removeSteps(index).run();
|
||||
// remove from URL hash too
|
||||
var urlHash = getUrlHashParameter('steps').split(',');
|
||||
urlHash.splice(index - 1, 1);
|
||||
setUrlHashParameter("steps", urlHash.join(','));
|
||||
}
|
||||
|
||||
$('body').on('click','button.remove', removeStepUI);
|
||||
|
||||
|
||||
// image selection and drag/drop handling from examples/lib/imageSelection.js
|
||||
setupFileHandling(sequencer);
|
||||
|
||||
sequencer.setInputStep({
|
||||
dropZoneSelector: "#dropzone",
|
||||
fileInputSelector: "#fileInput",
|
||||
onLoad: function onFileReaderLoad(progress) {
|
||||
var reader = progress.target;
|
||||
var step = sequencer.images.image1.steps[0];
|
||||
step.output.src = reader.result;
|
||||
sequencer.run({ index: 0 });
|
||||
step.options.step.imgElement.src = reader.result;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../dist/image-sequencer.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="../src/ui/prepareDynamic.js"></script>
|
||||
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
|
||||
<script src="lib/urlHash.js" charset="utf-8"></script>
|
||||
<script src="lib/imageSelection.js" charset="utf-8"></script>
|
||||
<script src="lib/defaultHtmlStepUi.js" charset="utf-8"></script>
|
||||
<script src="lib/defaultHtmlSequencerUi.js" charset="utf-8"></script>
|
||||
<script src="demo.js" charset="utf-8"></script>
|
||||
<!-- for crop module: -->
|
||||
<script src="../node_modules/imgareaselect/jquery.imgareaselect.dev.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -26,6 +30,10 @@
|
||||
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<!-- for crop module: -->
|
||||
<link href="../node_modules/imgareaselect/distfiles/css/imgareaselect-default.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<div class="container-fluid">
|
||||
@@ -33,17 +41,26 @@
|
||||
<header class="text-center">
|
||||
<h1>Image Sequencer</h1>
|
||||
<p>
|
||||
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original image, it creates a new image at each step in a sequence.
|
||||
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original image, it
|
||||
creates a new image at each step in a sequence.
|
||||
<a href="https://publiclab.org/image-sequencer">Learn more</a>
|
||||
</p>
|
||||
<p>
|
||||
Open Source <a href="https://github.com/publiclab/image-sequencer"><i class="fa fa-github"></i></a> by <a href="https://publiclab.org">Public Lab</a>
|
||||
Open Source
|
||||
<a href="https://github.com/publiclab/image-sequencer">
|
||||
<i class="fa fa-github"></i>
|
||||
</a> by
|
||||
<a href="https://publiclab.org">Public Lab</a>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div id="dropzone">
|
||||
<p><i>Select or drag in an image to start!</i></p>
|
||||
<center><input type="file" id="fileInput" value=""></center>
|
||||
<p>
|
||||
<i>Select or drag in an image to start!</i>
|
||||
</p>
|
||||
<center>
|
||||
<input type="file" id="fileInput" value="">
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<section id="steps" class="row"></section>
|
||||
@@ -54,7 +71,7 @@
|
||||
<div class="form-inline">
|
||||
<div class="panel-body">
|
||||
<div style="text-align:center;">
|
||||
<select class="form-control input-lg">
|
||||
<select class="form-control input-lg" id="selectStep">
|
||||
<option value="none" disabled selected>Select a new step...</option>
|
||||
</select>
|
||||
<button class="btn btn-success btn-lg" name="add">Add Step</button>
|
||||
@@ -64,13 +81,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<button class="btn btn-primary btn-lg" name="save-sequence" id="save-seq">Save Sequence</button>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(function() {
|
||||
var sequencer;
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
69
examples/lib/defaultHtmlSequencerUi.js
Normal file
69
examples/lib/defaultHtmlSequencerUi.js
Normal file
@@ -0,0 +1,69 @@
|
||||
function DefaultHtmlSequencerUi(_sequencer, options) {
|
||||
|
||||
options = options || {};
|
||||
var addStepSel = options.addStepSel = options.addStepSel || "#addStep";
|
||||
var removeStepSel = options.removeStepSel = options.removeStepSel || "button.remove";
|
||||
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
|
||||
|
||||
function onLoad() {
|
||||
importStepsFromUrlHash();
|
||||
if (!$('#selectStep').val())
|
||||
$(addStepSel + " button").prop("disabled", true);
|
||||
}
|
||||
|
||||
// look up needed steps from Url Hash:
|
||||
function importStepsFromUrlHash() {
|
||||
var hash = getUrlHashParameter("steps");
|
||||
|
||||
if (hash) {
|
||||
_sequencer.importString(hash);
|
||||
_sequencer.run({ index: 0 });
|
||||
}
|
||||
setUrlHashParameter("steps", sequencer.toString());
|
||||
}
|
||||
|
||||
function selectNewStepUi() {
|
||||
var m = $(addStepSel + " select").val();
|
||||
$(addStepSel + " .info").html(_sequencer.modulesInfo(m).description);
|
||||
$(addStepSel + " button").prop("disabled", false);
|
||||
}
|
||||
|
||||
function removeStepUi() {
|
||||
var index = $(removeStepSel).index(this) + 1;
|
||||
sequencer.removeSteps(index).run({ index: index - 1 });
|
||||
// remove from URL hash too
|
||||
setUrlHashParameter("steps", sequencer.toString());
|
||||
}
|
||||
|
||||
function addStepUi() {
|
||||
if ($(addStepSel + " select").val() == "none") return;
|
||||
|
||||
var newStepName = $(addStepSel + " select").val();
|
||||
|
||||
/*
|
||||
* after adding the step we run the sequencer from defined step
|
||||
* and since loadImage is not a part of the drawarray the step lies at current
|
||||
* length - 2 of the drawarray
|
||||
*/
|
||||
var sequenceLength = 1;
|
||||
if (sequencer.sequences[newStepName]) {
|
||||
sequenceLength = sequencer.sequences[newStepName].length;
|
||||
} else if (sequencer.modules[newStepName][1]["length"]) {
|
||||
sequenceLength = sequencer.modules[newStepName][1]["length"];
|
||||
}
|
||||
_sequencer
|
||||
.addSteps(newStepName, options)
|
||||
.run({ index: _sequencer.images.image1.steps.length - sequenceLength - 1 });
|
||||
|
||||
// add to URL hash too
|
||||
setUrlHashParameter("steps", _sequencer.toString());
|
||||
}
|
||||
|
||||
return {
|
||||
onLoad: onLoad,
|
||||
importStepsFromUrlHash: importStepsFromUrlHash,
|
||||
selectNewStepUi: selectNewStepUi,
|
||||
removeStepUi: removeStepUi,
|
||||
addStepUi: addStepUi
|
||||
}
|
||||
}
|
||||
194
examples/lib/defaultHtmlStepUi.js
Normal file
194
examples/lib/defaultHtmlStepUi.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// Set the UI in sequencer. This Will generate HTML based on
|
||||
// Image Sequencer events :
|
||||
// onSetup : Called every time a step is added
|
||||
// onDraw : Called every time a step starts draw
|
||||
// onComplete : Called every time a step finishes drawing
|
||||
// onRemove : Called everytime a step is removed
|
||||
// The variable 'step' stores useful data like input and
|
||||
// output values, step information.
|
||||
// See documetation for more details.
|
||||
function DefaultHtmlStepUi(_sequencer, options) {
|
||||
|
||||
options = options || {};
|
||||
var stepsEl = options.stepsEl || document.querySelector("#steps");
|
||||
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
|
||||
|
||||
function onSetup(step) {
|
||||
if (step.options && step.options.description)
|
||||
step.description = step.options.description;
|
||||
|
||||
step.ui =
|
||||
'\
|
||||
<div class="row step">\
|
||||
<div class="col-md-4 details">\
|
||||
<h3>' +
|
||||
step.name +
|
||||
"</h3>\
|
||||
<p><i>" +
|
||||
(step.description || "") +
|
||||
'</i></p>\
|
||||
</div>\
|
||||
<div class="col-md-8">\
|
||||
<div class="load" style="display:none;"><i class="fa fa-circle-o-notch fa-spin"></i></div>\
|
||||
<a><img alt="" style="max-width=100%" class="img-thumbnail" /></a>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn btn-default">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
</div>';
|
||||
|
||||
var parser = new DOMParser();
|
||||
step.ui = parser.parseFromString(step.ui, "text/html");
|
||||
step.ui = step.ui.querySelector("div.row");
|
||||
step.linkElement = step.ui.querySelector("a");
|
||||
step.imgElement = step.ui.querySelector("a img");
|
||||
|
||||
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = _sequencer.modulesInfo(step.name).outputs;
|
||||
var merged = Object.assign(inputs, outputs); // combine outputs w inputs
|
||||
|
||||
for (var paramName in merged) {
|
||||
var isInput = inputs.hasOwnProperty(paramName);
|
||||
var html = "";
|
||||
var inputDesc = isInput ? inputs[paramName] : {};
|
||||
if (!isInput) {
|
||||
html += '<span class="output"></span>';
|
||||
} else if (inputDesc.type.toLowerCase() == "select") {
|
||||
html += '<select class="form-control target" name="' + paramName + '">';
|
||||
for (var option in inputDesc.values) {
|
||||
html += "<option>" + inputDesc.values[option] + "</option>";
|
||||
}
|
||||
html += "</select>";
|
||||
} else {
|
||||
html =
|
||||
'<input class="form-control target" type="' +
|
||||
inputDesc.type +
|
||||
'" name="' +
|
||||
paramName +
|
||||
'">';
|
||||
}
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.className = "row";
|
||||
div.setAttribute("name", paramName);
|
||||
var description = inputs[paramName].desc || paramName;
|
||||
div.innerHTML =
|
||||
"<div class='det'>\
|
||||
<label for='" +
|
||||
paramName +
|
||||
"'>" +
|
||||
description +
|
||||
"</label>\
|
||||
" +
|
||||
html +
|
||||
"\
|
||||
</div>";
|
||||
step.ui.querySelector("div.details").appendChild(div);
|
||||
}
|
||||
|
||||
function toggleSaveButton(){
|
||||
$(step.ui.querySelector("div.details .btn-save")).prop("disabled",false);
|
||||
}
|
||||
|
||||
$(step.ui.querySelector(".target")).change(toggleSaveButton);
|
||||
|
||||
$(step.ui.querySelector("div.details")).append(
|
||||
"<p><button class='btn btn-default btn-save' disabled = 'true' >Save</button><span> Press save to see changes</span></p>"
|
||||
);
|
||||
|
||||
function saveOptions() {
|
||||
$(step.ui.querySelector("div.details"))
|
||||
.find("input,select")
|
||||
.each(function(i, input) {
|
||||
step.options[$(input).attr("name")] = input.value;
|
||||
});
|
||||
_sequencer.run({ index: step.index - 1 });
|
||||
|
||||
// modify the url hash
|
||||
setUrlHashParameter("steps", _sequencer.toString());
|
||||
// disable the save button
|
||||
$(step.ui.querySelector("div.details .btn-save")).prop("disabled",true);
|
||||
}
|
||||
|
||||
// on clicking Save in the details pane of the step
|
||||
$(step.ui.querySelector("div.details .btn-save")).click(saveOptions);
|
||||
}
|
||||
|
||||
if (step.name != "load-image")
|
||||
step.ui
|
||||
.querySelector("div.details")
|
||||
.appendChild(
|
||||
parser.parseFromString(tools, "text/html").querySelector("div")
|
||||
);
|
||||
|
||||
stepsEl.appendChild(step.ui);
|
||||
}
|
||||
|
||||
function onDraw(step) {
|
||||
$(step.ui.querySelector(".load")).show();
|
||||
$(step.ui.querySelector("img")).hide();
|
||||
}
|
||||
|
||||
function onComplete(step) {
|
||||
$(step.ui.querySelector(".load")).hide();
|
||||
$(step.ui.querySelector("img")).show();
|
||||
|
||||
step.imgElement.src = step.output;
|
||||
step.linkElement.href = step.output;
|
||||
|
||||
// TODO: use a generalized version of this
|
||||
function fileExtension(output) {
|
||||
return output.split("/")[1].split(";")[0];
|
||||
}
|
||||
|
||||
step.linkElement.download = step.name + "." + fileExtension(step.output);
|
||||
step.linkElement.target = "_blank";
|
||||
|
||||
// fill inputs with stored step options
|
||||
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = _sequencer.modulesInfo(step.name).outputs;
|
||||
for (var i in inputs) {
|
||||
if (
|
||||
step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "input"
|
||||
)
|
||||
step.ui.querySelector('div[name="' + i + '"] input').value =
|
||||
step.options[i];
|
||||
if (
|
||||
step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "select"
|
||||
)
|
||||
step.ui.querySelector('div[name="' + i + '"] select').value =
|
||||
step.options[i];
|
||||
}
|
||||
for (var i in outputs) {
|
||||
if (step[i] !== undefined)
|
||||
step.ui.querySelector('div[name="' + i + '"] input').value =
|
||||
step[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRemove(step) {
|
||||
step.ui.remove();
|
||||
}
|
||||
|
||||
function getPreview() {
|
||||
return step.imgElement;
|
||||
}
|
||||
|
||||
return {
|
||||
getPreview: getPreview,
|
||||
onSetup: onSetup,
|
||||
onComplete: onComplete,
|
||||
onRemove: onRemove,
|
||||
onDraw: onDraw
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
function setupFileHandling(_sequencer, dropzoneId, fileInputId) {
|
||||
|
||||
dropzoneId = dropzoneId || "dropzone";
|
||||
var dropzone = $('#' + dropzoneId);
|
||||
|
||||
fileInputId = fileInputId || "fileInput";
|
||||
var fileInput = $('#' + fileInputId);
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
function handleFile(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // stops the browser from redirecting.
|
||||
|
||||
if (e.target && e.target.files) var file = e.target.files[0];
|
||||
else var file = e.dataTransfer.files[0];
|
||||
if(!file) return;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function onFileReaderLoad() {
|
||||
var loadStep = _sequencer.images.image1.steps[0];
|
||||
loadStep.output.src = reader.result;
|
||||
_sequencer.run(0);
|
||||
loadStep.options.step.imgElement.src = reader.result;
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
fileInput.on('change', handleFile);
|
||||
|
||||
dropzone[0].addEventListener('drop', handleFile, false);
|
||||
|
||||
dropzone.on('dragover', function onDragover(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
}, false);
|
||||
|
||||
dropzone.on('dragenter', function onDragEnter(e) {
|
||||
dropzone.addClass('hover');
|
||||
});
|
||||
|
||||
dropzone.on('dragleave', function onDragLeave(e) {
|
||||
dropzone.removeClass('hover');
|
||||
});
|
||||
|
||||
}
|
||||
138
index.js
138
index.js
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('./src/ImageSequencer');
|
||||
require("./src/ImageSequencer");
|
||||
sequencer = ImageSequencer({ ui: false });
|
||||
var Spinner = require('ora')
|
||||
var Spinner = require("ora");
|
||||
|
||||
var program = require('commander');
|
||||
var readlineSync = require('readline-sync');
|
||||
var program = require("commander");
|
||||
var readlineSync = require("readline-sync");
|
||||
|
||||
function exit(message) {
|
||||
console.error(message);
|
||||
@@ -13,24 +13,37 @@ function exit(message) {
|
||||
}
|
||||
|
||||
program
|
||||
.version('0.1.0')
|
||||
.option('-i, --image [PATH/URL]', 'Input image URL')
|
||||
.option('-s, --step [step-name]', 'Name of the step to be added.')
|
||||
.option('-o, --output [PATH]', 'Directory where output will be stored.')
|
||||
.option('-b, --basic','Basic mode outputs only final image')
|
||||
.option('-c, --config [Object]', 'Options for the step')
|
||||
.version("0.1.0")
|
||||
.option("-i, --image [PATH/URL]", "Input image URL")
|
||||
.option("-s, --step [step-name]", "Name of the step to be added.")
|
||||
.option("-o, --output [PATH]", "Directory where output will be stored.")
|
||||
.option("-b, --basic", "Basic mode outputs only final image")
|
||||
.option("-c, --config [Object]", "Options for the step")
|
||||
.option("--save-sequence [string]", "Name space separated with Stringified sequence")
|
||||
.option('--install-module [string]', "Module name space seaprated npm package name")
|
||||
.parse(process.argv);
|
||||
|
||||
if (program.saveSequence) {
|
||||
var params = program.saveSequence.split(' ');
|
||||
sequencer.saveSequence(params[0], params[1]);
|
||||
} else if (program.installModule) {
|
||||
var params = program.installModule.split(' ');
|
||||
var spinner = Spinner("Now Installing...").start();
|
||||
require('child_process').execSync(`npm i ${params[1]}`)
|
||||
sequencer.saveNewModule(params[0], params[1]);
|
||||
sequencer.loadNewModule(params[0], require(params[1]));
|
||||
spinner.stop();
|
||||
} else {
|
||||
// Parse step into an array to allow for multiple steps.
|
||||
if(!program.step) exit("No steps passed")
|
||||
if (!program.step) exit("No steps passed");
|
||||
program.step = program.step.split(" ");
|
||||
|
||||
// User must input an image.
|
||||
if(!program.image) exit("Can't read file.")
|
||||
if (!program.image) exit("Can't read file.");
|
||||
|
||||
// User must input an image.
|
||||
require('fs').access(program.image, function(err){
|
||||
if(err) exit("Can't read file.")
|
||||
require("fs").access(program.image, function(err) {
|
||||
if (err) exit("Can't read file.");
|
||||
});
|
||||
|
||||
// User must input a step. If steps exist, check that every step is a valid step.
|
||||
@@ -42,29 +55,37 @@ program.output = program.output || "./output/";
|
||||
|
||||
// Set sequencer to log module outputs, if any.
|
||||
sequencer.setUI({
|
||||
|
||||
onComplete: function(step) {
|
||||
|
||||
// Get information of outputs.
|
||||
step.info = sequencer.modulesInfo(step.name);
|
||||
|
||||
for (var output in step.info.outputs) {
|
||||
console.log("[" + program.step + "]: " + output + " = " + step[output]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Finally, if everything is alright, load the image, add the steps and run the sequencer.
|
||||
sequencer.loadImages(program.image, function() {
|
||||
console.warn('\x1b[33m%s\x1b[0m', "Please wait \n output directory generated will be empty until the execution is complete")
|
||||
console.warn(
|
||||
"\x1b[33m%s\x1b[0m",
|
||||
"Please wait \n output directory generated will be empty until the execution is complete"
|
||||
);
|
||||
|
||||
//Generate the Output Directory
|
||||
require('./src/CliUtils').makedir(program.output,()=>{
|
||||
console.log("Files will be exported to \""+program.output+"\"");
|
||||
var outputFilename = program.output.split('/').slice(-1)[0];
|
||||
if (outputFilename.includes('.')) {
|
||||
// user did give an output filename we have to remove it from dir
|
||||
program.output = program.output.split('/').slice(0, -1).join('/');
|
||||
}
|
||||
else {
|
||||
outputFilename = null;
|
||||
}
|
||||
require("./src/CliUtils").makedir(program.output, () => {
|
||||
console.log('Files will be exported to "' + program.output + '"');
|
||||
|
||||
if(program.basic) console.log("Basic mode is enabled, outputting only final image")
|
||||
if (program.basic)
|
||||
console.log("Basic mode is enabled, outputting only final image");
|
||||
|
||||
// Iterate through the steps and retrieve their inputs.
|
||||
program.step.forEach(function(step) {
|
||||
@@ -79,30 +100,46 @@ sequencer.loadImages(program.image,function(){
|
||||
Object.keys(options).forEach(function(input) {
|
||||
// The array below creates a variable number of spaces. This is done with (length + 1).
|
||||
// The extra 4 that makes it (length + 5) is to account for the []: characters
|
||||
console.log(new Array(step.length + 5).join(' ') + input + ": " + options[input].desc);
|
||||
console.log(
|
||||
new Array(step.length + 5).join(" ") +
|
||||
input +
|
||||
": " +
|
||||
options[input].desc
|
||||
);
|
||||
});
|
||||
|
||||
if (program.config) {
|
||||
try {
|
||||
program.config = JSON.parse(program.config);
|
||||
console.log(`The parsed options object: `, program.config);
|
||||
}
|
||||
catch(e){
|
||||
console.error('\x1b[31m%s\x1b[0m',`Options(Config) is not a not valid JSON Fallback activate`);
|
||||
var config = JSON.parse(program.config);
|
||||
console.log(`The parsed options object: `, config);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"\x1b[31m%s\x1b[0m",
|
||||
`Options(Config) is not a not valid JSON Fallback activate`
|
||||
);
|
||||
program.config = false;
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
if(program.config && validateConfig(program.config,options)){
|
||||
if (program.config && validateConfig(config, options)) {
|
||||
console.log("Now using Options object");
|
||||
Object.keys(options).forEach(function(input) {
|
||||
options[input] = program.config[input];
|
||||
})
|
||||
}
|
||||
else{
|
||||
options[input] = config[input];
|
||||
});
|
||||
} else {
|
||||
// If inputs exist, iterate through them and prompt for values.
|
||||
Object.keys(options).forEach(function(input) {
|
||||
var value = readlineSync.question("[" + step + "]: Enter a value for " + input + " (" + options[input].type + ", default: " + options[input].default + "): ");
|
||||
var value = readlineSync.question(
|
||||
"[" +
|
||||
step +
|
||||
"]: Enter a value for " +
|
||||
input +
|
||||
" (" +
|
||||
options[input].type +
|
||||
", default: " +
|
||||
options[input].default +
|
||||
"): "
|
||||
);
|
||||
options[input] = value;
|
||||
});
|
||||
}
|
||||
@@ -110,29 +147,26 @@ sequencer.loadImages(program.image,function(){
|
||||
sequencer.addSteps(step, options);
|
||||
});
|
||||
|
||||
var spinnerObj = Spinner('Your Image is being processed..').start();
|
||||
var spinnerObj = { succeed: () => { }, stop: () => { } };
|
||||
if (!process.env.TEST)
|
||||
spinnerObj = Spinner("Your Image is being processed..").start();
|
||||
|
||||
// Run the sequencer.
|
||||
sequencer.run(spinnerObj,function(){
|
||||
|
||||
sequencer.run({ progressObj: spinnerObj }, function() {
|
||||
// Export all images or final image as binary files.
|
||||
sequencer.exportBin(program.output,program.basic);
|
||||
sequencer.exportBin(program.output, program.basic, outputFilename);
|
||||
|
||||
//check if spinner was not overriden stop it
|
||||
if (!spinnerObj.overrideFlag) {
|
||||
spinnerObj.succeed()
|
||||
console.log(`\nDone!!`)
|
||||
spinnerObj.succeed();
|
||||
console.log(`\nDone!!`);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Takes an array of steps and checks if they are valid steps for the sequencer.
|
||||
function validateSteps(steps) {
|
||||
|
||||
// Assume all are valid in the beginning.
|
||||
var valid = true;
|
||||
steps.forEach(function(step) {
|
||||
@@ -153,13 +187,19 @@ function validateConfig(config_,options_){
|
||||
(function() {
|
||||
for (var input in options_) {
|
||||
if (!config_[options_[input]]) {
|
||||
console.error('\x1b[31m%s\x1b[0m',`Options Object does not have the required details "${options_[input]}" not specified. Fallback case activated`);
|
||||
console.error(
|
||||
"\x1b[31m%s\x1b[0m",
|
||||
`Options Object does not have the required details "${
|
||||
options_[input]
|
||||
}" not specified. Fallback case activated`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})()
|
||||
== false)
|
||||
})() == false
|
||||
)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
else return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8059
package-lock.json
generated
Normal file
8059
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "image-sequencer",
|
||||
"version": "1.3.0",
|
||||
"version": "2.2.3",
|
||||
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"debug": "node ./index.js -i ./examples/images/monarch.png -s",
|
||||
"test": "tape test/**/*.js test/*.js | tap-spec; browserify test/modules/image-sequencer.js test/modules/chain.js test/modules/replace.js | tape-run --render=\"tap-spec\""
|
||||
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
|
||||
"test": "TEST=true tape test/**/*.js test/*.js | tap-spec; browserify test/modules/image-sequencer.js test/modules/chain.js test/modules/meta-modules.js test/modules/replace.js test/modules/import-export.js test/modules/run.js test/modules/dynamic-imports.js | tape-run --render=\"tap-spec\"",
|
||||
"start": "grunt serve"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,6 +29,8 @@
|
||||
"fisheyegl": "^0.1.2",
|
||||
"font-awesome": "~4.5.0",
|
||||
"get-pixels": "~3.3.0",
|
||||
"image-sequencer-invert": "^1.0.0",
|
||||
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2",
|
||||
"jquery": "~2",
|
||||
"jsqr": "^0.2.2",
|
||||
"lodash": "^4.17.5",
|
||||
@@ -40,11 +43,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "13.0.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browser-sync": "^2.2.0",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-uglify-es": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony",
|
||||
"grunt-contrib-uglify-es": "^3.3.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"image-filter-core": "~1.0.0",
|
||||
"image-filter-threshold": "~1.0.0",
|
||||
|
||||
@@ -1,34 +1,5 @@
|
||||
// add steps to the sequencer
|
||||
function AddStep(ref, image, name, o) {
|
||||
|
||||
function addStep(image, name, o_) {
|
||||
var moduleInfo = ref.modules[name][1];
|
||||
|
||||
var o = ref.copy(o_);
|
||||
o.number = ref.options.sequencerCounter++; // gives a unique ID to each step
|
||||
o.name = o_.name || name || moduleInfo.name;
|
||||
o.description = o_.description || moduleInfo.description;
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.image = image;
|
||||
o.inBrowser = ref.options.inBrowser;
|
||||
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](o,UI);
|
||||
ref.images[image].steps.push(module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addStep(image, name, o);
|
||||
function AddStep(_sequencer, image, name, o) {
|
||||
return require('./InsertStep')(_sequencer,image,-1,name,o);
|
||||
}
|
||||
module.exports = AddStep;
|
||||
|
||||
@@ -23,12 +23,24 @@ var getDirectories = function(rootDir, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function ExportBin(dir = "./output/",ref,basic) {
|
||||
module.exports = function ExportBin(dir = "./output/", ref, basic, filename) {
|
||||
|
||||
// If user did not give an output filename so we can continue without doing anything
|
||||
dir = (dir[dir.length - 1] == "/") ? dir : dir + "/";
|
||||
if (ref.options.inBrowser) return false;
|
||||
fs.access(dir, function(err) {
|
||||
if (err) console.error(err)
|
||||
});
|
||||
if (filename && basic) {
|
||||
for (var image in ref.images) {
|
||||
var steps = ref.images[image].steps;
|
||||
var datauri = steps.slice(-1)[0].output.src;
|
||||
var ext = steps.slice(-1)[0].output.format;
|
||||
var buffer = require('data-uri-to-buffer')(datauri);
|
||||
fs.writeFile(dir + filename, buffer, function() { });
|
||||
}
|
||||
}
|
||||
else {
|
||||
getDirectories(dir, function(dirs) {
|
||||
var num = 1;
|
||||
for (var d in dirs) {
|
||||
@@ -55,6 +67,7 @@ module.exports = function ExportBin(dir = "./output/",ref,basic) {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
if (typeof window !== 'undefined') {window.$ = window.jQuery = require('jquery'); isBrowser = true}
|
||||
if (typeof window !== 'undefined') { isBrowser = true }
|
||||
else { var isBrowser = false }
|
||||
require('./util/getStep.js');
|
||||
|
||||
ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
var sequencer = (this.name == "ImageSequencer") ? this : this.sequencer;
|
||||
options = options || {};
|
||||
options.inBrowser = options.inBrowser || isBrowser;
|
||||
options.sequencerCounter = 0;
|
||||
@@ -38,12 +40,26 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
var image,
|
||||
steps = [],
|
||||
modules = require('./Modules'),
|
||||
sequences = require('./SavedSequences.json'),
|
||||
formatInput = require('./FormatInput'),
|
||||
images = {},
|
||||
inputlog = [],
|
||||
events = require('./ui/UserInterface')(),
|
||||
fs = require('fs');
|
||||
|
||||
|
||||
|
||||
if (options.inBrowser) {
|
||||
for (o in sequencer) {
|
||||
modules[o] = sequencer[o];
|
||||
}
|
||||
sequences = JSON.parse(window.localStorage.getItem('sequences'));
|
||||
if (!sequences) {
|
||||
sequences = {};
|
||||
window.localStorage.setItem('sequences', JSON.stringify(sequences));
|
||||
}
|
||||
}
|
||||
|
||||
// if in browser, prompt for an image
|
||||
// if (options.imageSelect || options.inBrowser) addStep('image-select');
|
||||
// else if (options.imageUrl) loadImage(imageUrl);
|
||||
@@ -113,11 +129,21 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
return this;
|
||||
}
|
||||
|
||||
function run(spinnerObj,t_image,t_from) {
|
||||
let progressObj;
|
||||
if(arguments[0] != 'test'){
|
||||
progressObj = spinnerObj
|
||||
delete arguments['0']
|
||||
// Config is an object which contains the runtime configuration like progress bar
|
||||
// information and index from which the sequencer should run
|
||||
function run(config, t_image, t_from) {
|
||||
let progressObj, index = 0;
|
||||
config = config || { mode: 'no-arg' };
|
||||
if (config.index) index = config.index;
|
||||
|
||||
if (config.mode != 'test') {
|
||||
if (config.mode != "no-arg" && typeof config != 'function') {
|
||||
if (config.progressObj) progressObj = config.progressObj;
|
||||
delete arguments['0'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
arguments['0'] = config.mode;
|
||||
}
|
||||
|
||||
var this_ = (this.name == "ImageSequencer") ? this : this.sequencer;
|
||||
@@ -131,7 +157,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
var json_q = formatInput.call(this_, args, "r");
|
||||
|
||||
require('./Run')(this_, json_q, callback,progressObj);
|
||||
require('./Run')(this_, json_q, callback, index, progressObj);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -181,27 +207,224 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
this.events = require('./ui/UserInterface')(UI);
|
||||
}
|
||||
|
||||
var exportBin = function(dir,basic) {
|
||||
return require('./ExportBin')(dir,this,basic);
|
||||
var exportBin = function(dir, basic, filename) {
|
||||
return require('./ExportBin')(dir, this, basic, filename);
|
||||
}
|
||||
|
||||
function modulesInfo(name) {
|
||||
var modulesdata = {}
|
||||
if (name == "load-image") return {};
|
||||
if(arguments.length==0)
|
||||
for (var modulename in modules) {
|
||||
if (arguments.length == 0) {
|
||||
for (var modulename in this.modules) {
|
||||
modulesdata[modulename] = modules[modulename][1];
|
||||
}
|
||||
else modulesdata = modules[name][1];
|
||||
for (var sequencename in this.sequences) {
|
||||
modulesdata[sequencename] = { name: sequencename, steps: sequences[sequencename] };
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (modules[name])
|
||||
modulesdata = modules[name][1];
|
||||
else
|
||||
modulesdata = { 'inputs': sequences[name]['options'] };
|
||||
}
|
||||
return modulesdata;
|
||||
}
|
||||
|
||||
// Genates a CLI string for the current sequence
|
||||
function toCliString() {
|
||||
var cliStringSteps = `"`, cliOptions = {};
|
||||
for (var step in this.steps) {
|
||||
if (this.steps[step].options.name !== "load-image")
|
||||
cliStringSteps += `${this.steps[step].options.name} `;
|
||||
for (var inp in modulesInfo(this.steps[step].options.name).inputs) {
|
||||
cliOptions[inp] = this.steps[step].options[inp];
|
||||
}
|
||||
}
|
||||
cliStringSteps = cliStringSteps.substr(0, cliStringSteps.length - 1) + `"`;
|
||||
return `sequencer -i [PATH] -s ${cliStringSteps} -d '${JSON.stringify(cliOptions)}'`
|
||||
}
|
||||
|
||||
// Strigifies the current sequence
|
||||
function toString(step) {
|
||||
if (step) {
|
||||
return stepToString(step);
|
||||
} else {
|
||||
return copy(this.images.image1.steps).map(stepToString).slice(1).join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Stringifies one step of the sequence
|
||||
function stepToString(step) {
|
||||
let inputs = copy(modulesInfo(step.options.name).inputs);
|
||||
inputs = inputs || {};
|
||||
|
||||
for (let input in inputs) {
|
||||
inputs[input] = step.options[input] || inputs[input].default;
|
||||
inputs[input] = encodeURIComponent(inputs[input]);
|
||||
}
|
||||
|
||||
var configurations = Object.keys(inputs).map(key => key + ':' + inputs[key]).join('|');
|
||||
return `${step.options.name}{${configurations}}`;
|
||||
}
|
||||
|
||||
// exports the current sequence as an array of JSON steps
|
||||
function toJSON() {
|
||||
return this.stringToJSON(this.toString());
|
||||
}
|
||||
|
||||
// Coverts stringified sequence into an array of JSON steps
|
||||
function stringToJSON(str) {
|
||||
let steps;
|
||||
if (str.includes(','))
|
||||
steps = str.split(',');
|
||||
else
|
||||
steps = [str];
|
||||
return steps.map(stringToJSONstep);
|
||||
}
|
||||
|
||||
// Converts one stringified step into JSON
|
||||
function stringToJSONstep(str) {
|
||||
var bracesStrings;
|
||||
if (str.includes('{'))
|
||||
if (str.includes('(') && str.indexOf('(') < str.indexOf('{'))
|
||||
bracesStrings = ['(', ')'];
|
||||
else
|
||||
bracesStrings = ['{', '}'];
|
||||
else
|
||||
bracesStrings = ['(', ')'];
|
||||
|
||||
if (str.indexOf(bracesStrings[0]) === -1) { // if there are no settings specified
|
||||
var moduleName = str.substr(0);
|
||||
stepSettings = "";
|
||||
} else {
|
||||
var moduleName = str.substr(0, str.indexOf(bracesStrings[0]));
|
||||
stepSettings = str.slice(str.indexOf(bracesStrings[0]) + 1, -1);
|
||||
}
|
||||
|
||||
stepSettings = stepSettings.split('|').reduce(function formatSettings(accumulator, current, i) {
|
||||
var settingName = current.substr(0, current.indexOf(':')),
|
||||
settingValue = current.substr(current.indexOf(':') + 1);
|
||||
settingValue = settingValue.replace(/^\(/, '').replace(/\)$/, ''); // strip () at start/end
|
||||
settingValue = settingValue.replace(/^\{/, '').replace(/\}$/, ''); // strip {} at start/end
|
||||
settingValue = decodeURIComponent(settingValue);
|
||||
current = [
|
||||
settingName,
|
||||
settingValue
|
||||
];
|
||||
if (!!settingName) accumulator[settingName] = settingValue;
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
name: moduleName,
|
||||
options: stepSettings
|
||||
}
|
||||
}
|
||||
|
||||
// imports a string into the sequencer steps
|
||||
function importString(str) {
|
||||
let sequencer = this;
|
||||
if (this.name != "ImageSequencer")
|
||||
sequencer = this.sequencer;
|
||||
var stepsFromString = stringToJSON(str);
|
||||
stepsFromString.forEach(function eachStep(stepObj) {
|
||||
sequencer.addSteps(stepObj.name, stepObj.options);
|
||||
});
|
||||
}
|
||||
|
||||
// imports a array of JSON steps into the sequencer steps
|
||||
function importJSON(obj) {
|
||||
let sequencer = this;
|
||||
if (this.name != "ImageSequencer")
|
||||
sequencer = this.sequencer;
|
||||
obj.forEach(function eachStep(stepObj) {
|
||||
sequencer.addSteps(stepObj.name, stepObj.options);
|
||||
});
|
||||
}
|
||||
|
||||
function loadNewModule(name, options) {
|
||||
|
||||
if (!options) {
|
||||
return this;
|
||||
|
||||
} else if (Array.isArray(options)) {
|
||||
// contains the array of module and info
|
||||
this.modules[name] = options;
|
||||
|
||||
} else if (options.func && options.info) {
|
||||
// passed in options object
|
||||
this.modules[name] = [
|
||||
options.func, options.info
|
||||
];
|
||||
|
||||
} else if (options.path && !this.inBrowser) {
|
||||
// load from path(only in node)
|
||||
const module = [
|
||||
require(`${options.path}/Module.js`),
|
||||
require(`${options.path}/info.json`)
|
||||
];
|
||||
this.modules[name] = module;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
function saveNewModule(name, path) {
|
||||
if (options.inBrowser) {
|
||||
// Not for browser context
|
||||
return;
|
||||
}
|
||||
var mods = fs.readFileSync('./src/Modules.js').toString();
|
||||
mods = mods.substr(0, mods.length - 1) + " '" + name + "': require('" + path + "'),\n}";
|
||||
fs.writeFileSync('./src/Modules.js', mods);
|
||||
}
|
||||
|
||||
function createMetaModule(stepsCollection, info) {
|
||||
var stepsArr = stepsCollection;
|
||||
if (typeof stepsCollection === 'string')
|
||||
stepsArr = stringToJSON(stepsCollection);
|
||||
var metaMod = function() {
|
||||
this.expandSteps(stepsArr);
|
||||
return {
|
||||
isMeta: true
|
||||
}
|
||||
}
|
||||
return [metaMod, info];
|
||||
}
|
||||
|
||||
function saveSequence(name, sequenceString) {
|
||||
const sequence = stringToJSON(sequenceString);
|
||||
// Save the given sequence string as a module
|
||||
if (options.inBrowser) {
|
||||
// Inside the browser we save the meta-modules using the Web Storage API
|
||||
var sequences = JSON.parse(window.localStorage.getItem('sequences'));
|
||||
sequences[name] = sequence;
|
||||
window.localStorage.setItem('sequences', JSON.stringify(sequences));
|
||||
}
|
||||
else {
|
||||
// In node we save the sequences in the json file SavedSequences.json
|
||||
var sequences = require('./SavedSequences.json');
|
||||
sequences[name] = sequence;
|
||||
fs.writeFileSync('./src/SavedSequences.json', JSON.stringify(sequences));
|
||||
}
|
||||
}
|
||||
|
||||
function loadModules() {
|
||||
// This function loads the modules and saved sequences
|
||||
this.modules = require('./Modules');
|
||||
if (options.inBrowser)
|
||||
this.sequences = JSON.parse(window.localStorage.getItem('sequences'));
|
||||
else
|
||||
this.sequences = require('./SavedSequences.json');
|
||||
}
|
||||
|
||||
return {
|
||||
//literals and objects
|
||||
name: "ImageSequencer",
|
||||
options: options,
|
||||
inputlog: inputlog,
|
||||
modules: modules,
|
||||
sequences: sequences,
|
||||
images: images,
|
||||
events: events,
|
||||
|
||||
@@ -216,11 +439,26 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
setUI: setUI,
|
||||
exportBin: exportBin,
|
||||
modulesInfo: modulesInfo,
|
||||
toCliString: toCliString,
|
||||
toString: toString,
|
||||
stepToString: stepToString,
|
||||
toJSON: toJSON,
|
||||
stringToJSON: stringToJSON,
|
||||
stringToJSONstep: stringToJSONstep,
|
||||
importString: importString,
|
||||
importJSON: importJSON,
|
||||
loadNewModule: loadNewModule,
|
||||
saveNewModule: saveNewModule,
|
||||
createMetaModule: createMetaModule,
|
||||
saveSequence: saveSequence,
|
||||
loadModules: loadModules,
|
||||
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy
|
||||
copy: copy,
|
||||
|
||||
setInputStep: require('./ui/SetInputStep')(sequencer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
const getStepUtils = require('./util/getStep.js');
|
||||
|
||||
// insert one or more steps at a given index in the sequencer
|
||||
function InsertStep(ref, image, index, name, o) {
|
||||
if (ref.sequences[name]) {
|
||||
return ref.importJSON(ref.sequences[name]);
|
||||
}
|
||||
|
||||
function insertStep(image, index, name, o_) {
|
||||
if (ref.modules[name]) var moduleInfo = ref.modules[name][1];
|
||||
else {
|
||||
console.log('Module ' + name + ' not found.');
|
||||
}
|
||||
|
||||
var o = ref.copy(o_);
|
||||
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o_.name || name;
|
||||
o.name = o_.name || name || moduleInfo.name;
|
||||
o.description = o_.description || moduleInfo.description;
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.image = image;
|
||||
o.inBrowser = ref.options.inBrowser;
|
||||
|
||||
if (index == -1) index = ref.images[image].steps.length;
|
||||
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
url: o.url,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
@@ -22,12 +33,26 @@ function InsertStep(ref, image, index, name, o) {
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](o,UI);
|
||||
ref.images[image].steps.splice(index,0,module);
|
||||
|
||||
// Tell UI that a step has been set up.
|
||||
o = o || {};
|
||||
ref.modules[name].expandSteps = function expandSteps(stepsArray) {
|
||||
for (var step of stepsArray) {
|
||||
ref.addSteps(step['name'], step['options']);
|
||||
}
|
||||
}
|
||||
if (!ref.modules[name][1].length) {
|
||||
UI.onSetup(o.step);
|
||||
ref.images[image].steps.splice(index, 0, ref.modules[name][0](o, UI));
|
||||
} else {
|
||||
ref.modules[name][0](o, UI);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
insertStep(image, index, name, o);
|
||||
ref.steps = ref.images[image].steps;
|
||||
|
||||
}
|
||||
module.exports = InsertStep;
|
||||
|
||||
@@ -2,40 +2,23 @@
|
||||
* Core modules and their info files
|
||||
*/
|
||||
module.exports = {
|
||||
'channel': [
|
||||
require('./modules/Channel/Module'),require('./modules/Channel/info')
|
||||
],
|
||||
'brightness': [
|
||||
require('./modules/Brightness/Module'),require('./modules/Brightness/info')
|
||||
],
|
||||
'edge-detect':[
|
||||
require('./modules/EdgeDetect/Module'),require('./modules/EdgeDetect/info')
|
||||
],
|
||||
'ndvi': [
|
||||
require('./modules/Ndvi/Module'),require('./modules/Ndvi/info')
|
||||
],
|
||||
'invert': [
|
||||
require('./modules/Invert/Module'),require('./modules/Invert/info')
|
||||
],
|
||||
'crop': [
|
||||
require('./modules/Crop/Module'),require('./modules/Crop/info')
|
||||
],
|
||||
'colormap': [
|
||||
require('./modules/Colormap/Module'),require('./modules/Colormap/info')
|
||||
],
|
||||
'decode-qr': [
|
||||
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
|
||||
],
|
||||
'fisheye-gl': [
|
||||
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
|
||||
],
|
||||
'dynamic': [
|
||||
require('./modules/Dynamic/Module'),require('./modules/Dynamic/info')
|
||||
],
|
||||
'blur': [
|
||||
require('./modules/Blur/Module'),require('./modules/Blur/info')
|
||||
],
|
||||
'saturation': [
|
||||
require('./modules/Saturation/Module'),require('./modules/Saturation/info')
|
||||
]
|
||||
'channel': require('./modules/Channel'),
|
||||
'brightness': require('./modules/Brightness'),
|
||||
'edge-detect': require('./modules/EdgeDetect'),
|
||||
'ndvi': require('./modules/Ndvi'),
|
||||
'crop': require('./modules/Crop'),
|
||||
'colormap': require('./modules/Colormap'),
|
||||
'decode-qr': require('./modules/DecodeQr'),
|
||||
'fisheye-gl': require('./modules/FisheyeGl'),
|
||||
'dynamic': require('./modules/Dynamic'),
|
||||
'blur': require('./modules/Blur'),
|
||||
'saturation': require('./modules/Saturation'),
|
||||
'average': require('./modules/Average'),
|
||||
'blend': require('./modules/Blend'),
|
||||
'import-image': require('./modules/ImportImage'),
|
||||
'overlay': require('./modules/Overlay'),
|
||||
'gradient': require('./modules/Gradient'),
|
||||
'invert': require('image-sequencer-invert'),
|
||||
'ndvi-colormap': require('./modules/NdviColormap'),
|
||||
'colorbar': require('./modules/Colorbar'),
|
||||
}
|
||||
@@ -4,7 +4,8 @@ function ReplaceImage(ref,selector,steps,options) {
|
||||
if(!ref.options.inBrowser) return false; // This isn't for Node.js
|
||||
var tempSequencer = ImageSequencer({ui: false});
|
||||
var this_ = ref;
|
||||
var input = document.querySelectorAll(selector);
|
||||
if (window.hasOwnProperty('$')) var input = $(selector);
|
||||
else var input = document.querySelectorAll(selector);
|
||||
var images = [];
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (input[i] instanceof HTMLImageElement) images.push(input[i]);
|
||||
@@ -12,14 +13,25 @@ function ReplaceImage(ref,selector,steps,options) {
|
||||
|
||||
function replaceImage (img, steps) {
|
||||
var url = img.src;
|
||||
var ext = url.split('.').pop();
|
||||
// refactor to filetypeFromUrl()
|
||||
var ext = url.split('?')[0].split('.').pop();
|
||||
|
||||
var xmlHTTP = new XMLHttpRequest();
|
||||
xmlHTTP.open('GET', url, true);
|
||||
xmlHTTP.responseType = 'arraybuffer';
|
||||
xmlHTTP.onload = function(e) {
|
||||
var arr = new Uint8Array(this.response);
|
||||
var raw = String.fromCharCode.apply(null,arr);
|
||||
|
||||
// in chunks to avoid "RangeError: Maximum call stack exceeded"
|
||||
// https://github.com/publiclab/image-sequencer/issues/241
|
||||
// https://stackoverflow.com/a/20048852/1116657
|
||||
var raw = '';
|
||||
var i,j,subArray,chunk = 5000;
|
||||
for (i=0,j=arr.length; i<j; i+=chunk) {
|
||||
subArray = arr.subarray(i,i+chunk);
|
||||
raw += String.fromCharCode.apply(null, subArray);
|
||||
}
|
||||
|
||||
var base64 = btoa(raw);
|
||||
var dataURL="data:image/"+ext+";base64," + base64;
|
||||
make(dataURL);
|
||||
|
||||
55
src/Run.js
55
src/Run.js
@@ -1,24 +1,59 @@
|
||||
function Run(ref, json_q, callback,progressObj) {
|
||||
if(!progressObj) progressObj = {stop: function(){}}
|
||||
const getStepUtils = require('./util/getStep.js');
|
||||
|
||||
function Run(ref, json_q, callback, ind, progressObj) {
|
||||
if (!progressObj) progressObj = { stop: function() { } };
|
||||
|
||||
function drawStep(drawarray, pos) {
|
||||
if (pos == drawarray.length && drawarray[pos - 1] !== undefined) {
|
||||
var image = drawarray[pos - 1].image;
|
||||
if(ref.objTypeOf(callback) == 'Function') {
|
||||
if (ref.objTypeOf(callback) == "Function" && ref.images[image].steps.slice(-1)[0].output) {
|
||||
var steps = ref.images[image].steps;
|
||||
var out = steps[steps.length - 1].output.src;
|
||||
callback(out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// so we don't run on the loadImage module:
|
||||
if (drawarray[pos] !== undefined) {
|
||||
var image = drawarray[pos].image;
|
||||
var i = drawarray[pos].i;
|
||||
var input = ref.images[image].steps[i - 1].output;
|
||||
ref.images[image].steps[i].draw(ref.copy(input), function onEachStep() {
|
||||
|
||||
ref.images[image].steps[i].getStep = function getStep(offset) {
|
||||
if (i + offset >= ref.images[image].steps.length) return { options: { name: undefined } };
|
||||
else return ref.images[image].steps.slice(i + offset)[0];
|
||||
};
|
||||
ref.images[image].steps[i].getIndex = function getIndex() {
|
||||
return i;
|
||||
}
|
||||
|
||||
for (var util in getStepUtils) {
|
||||
if (getStepUtils.hasOwnProperty(util)) {
|
||||
ref.images[image].steps[i][util] = getStepUtils[util];
|
||||
}
|
||||
}
|
||||
|
||||
// Tell UI that a step is being drawn.
|
||||
ref.images[image].steps[i].UI.onDraw(ref.images[image].steps[i].options.step);
|
||||
|
||||
// provides a set of standard tools for each step
|
||||
var inputForNextStep = require('./RunToolkit')(ref.copy(input));
|
||||
|
||||
ref.images[image].steps[i].draw(
|
||||
inputForNextStep,
|
||||
function onEachStep() {
|
||||
|
||||
// This output is accessible by UI
|
||||
ref.images[image].steps[i].options.step.output = ref.images[image].steps[i].output.src;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
ref.images[image].steps[i].UI.onComplete(ref.images[image].steps[i].options.step);
|
||||
|
||||
drawStep(drawarray, ++pos);
|
||||
},progressObj);
|
||||
},
|
||||
progressObj
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +66,7 @@ function Run(ref, json_q, callback,progressObj) {
|
||||
drawarray.push({ image: image, i: init + i });
|
||||
}
|
||||
}
|
||||
drawStep(drawarray,0);
|
||||
drawStep(drawarray, ind);
|
||||
}
|
||||
|
||||
function filter(json_q) {
|
||||
@@ -42,8 +77,11 @@ function Run(ref, json_q, callback,progressObj) {
|
||||
}
|
||||
for (var image in json_q) {
|
||||
var prevstep = ref.images[image].steps[json_q[image] - 1];
|
||||
while (typeof(prevstep) == "undefined" || typeof(prevstep.output) == "undefined") {
|
||||
prevstep = ref.images[image].steps[(--json_q[image]) - 1];
|
||||
while (
|
||||
typeof prevstep == "undefined" ||
|
||||
typeof prevstep.output == "undefined"
|
||||
) {
|
||||
prevstep = ref.images[image].steps[--json_q[image] - 1];
|
||||
}
|
||||
}
|
||||
return json_q;
|
||||
@@ -51,6 +89,5 @@ function Run(ref, json_q, callback,progressObj) {
|
||||
|
||||
var json_q = filter(json_q);
|
||||
return drawSteps(json_q);
|
||||
|
||||
}
|
||||
module.exports = Run;
|
||||
|
||||
14
src/RunToolkit.js
Normal file
14
src/RunToolkit.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const getPixels = require('get-pixels');
|
||||
const pixelManipulation = require('./modules/_nomodule/PixelManipulation');
|
||||
const lodash = require('lodash');
|
||||
const dataUriToBuffer = require('data-uri-to-buffer');
|
||||
const savePixels = require('save-pixels');
|
||||
|
||||
module.exports = function(input) {
|
||||
input.getPixels = getPixels;
|
||||
input.pixelManipulation = pixelManipulation;
|
||||
input.lodash = lodash;
|
||||
input.dataUriToBuffer = dataUriToBuffer;
|
||||
input.savePixels = savePixels;
|
||||
return input;
|
||||
}
|
||||
1
src/SavedSequences.json
Normal file
1
src/SavedSequences.json
Normal file
@@ -0,0 +1 @@
|
||||
{"sample":[{"name":"invert","options":{}},{"name":"channel","options":{"channel":"red"}},{"name":"blur","options":{"blur":"5"}}]}
|
||||
76
src/modules/Average/Module.js
Executable file
76
src/modules/Average/Module.js
Executable file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Average all pixel colors
|
||||
*/
|
||||
module.exports = function Average(options, UI){
|
||||
|
||||
options.blur = options.blur || 2
|
||||
var output;
|
||||
|
||||
options.step.metadata = options.step.metadata || {};
|
||||
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a){
|
||||
return [r,g,b,a]
|
||||
}
|
||||
|
||||
// do the averaging
|
||||
function extraManipulation(pixels){
|
||||
var sum = [0,0,0,0];
|
||||
for (var i = 0; i < pixels.data.length; i += 4) {
|
||||
sum[0] += pixels.data[i + 0];
|
||||
sum[1] += pixels.data[i + 1];
|
||||
sum[2] += pixels.data[i + 2];
|
||||
sum[3] += pixels.data[i + 3];
|
||||
}
|
||||
|
||||
sum[0] = parseInt(sum[0] / (pixels.data.length / 4));
|
||||
sum[1] = parseInt(sum[1] / (pixels.data.length / 4));
|
||||
sum[2] = parseInt(sum[2] / (pixels.data.length / 4));
|
||||
sum[3] = parseInt(sum[3] / (pixels.data.length / 4));
|
||||
|
||||
for (var i = 0; i < pixels.data.length; i += 4) {
|
||||
pixels.data[i + 0] = sum[0];
|
||||
pixels.data[i + 1] = sum[1];
|
||||
pixels.data[i + 2] = sum[2];
|
||||
pixels.data[i + 3] = sum[3];
|
||||
}
|
||||
// report back and store average in metadata:
|
||||
options.step.metadata.averages = sum;
|
||||
console.log("average: ", sum);
|
||||
// TODO: refactor into a new "display()" method as per https://github.com/publiclab/image-sequencer/issues/242
|
||||
if (options.step.inBrowser && options.step.ui) $(options.step.ui).find('.details').append("<p><b>Averages</b> (r, g, b, a): " + sum.join(', ') + "</p>");
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype){
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {
|
||||
src: datauri,
|
||||
format: mimetype
|
||||
};
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
4
src/modules/Average/index.js
Normal file
4
src/modules/Average/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
6
src/modules/Average/info.json
Executable file
6
src/modules/Average/info.json
Executable file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Average",
|
||||
"description": "Average all pixel color",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
63
src/modules/Blend/Module.js
Normal file
63
src/modules/Blend/Module.js
Normal file
@@ -0,0 +1,63 @@
|
||||
module.exports = function Dynamic(options, UI, util) {
|
||||
|
||||
options.func = options.func || "function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }";
|
||||
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
var step = this;
|
||||
|
||||
// convert to runnable code:
|
||||
if (typeof options.func === "string") eval('options.func = ' + options.func);
|
||||
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
// save first image's pixels
|
||||
var priorStep = this.getStep(-2);
|
||||
|
||||
getPixels(priorStep.output.src, function(err, pixels) {
|
||||
options.firstImagePixels = pixels;
|
||||
|
||||
function changePixel(r2, g2, b2, a2, x, y) {
|
||||
// blend!
|
||||
var p = options.firstImagePixels;
|
||||
return options.func(
|
||||
r2, g2, b2, a2,
|
||||
p.get(x, y, 0),
|
||||
p.get(x, y, 1),
|
||||
p.get(x, y, 2),
|
||||
p.get(x, y, 3)
|
||||
)
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
// run PixelManipulatin on second image's pixels
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
4
src/modules/Blend/index.js
Normal file
4
src/modules/Blend/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
11
src/modules/Blend/info.json
Executable file
11
src/modules/Blend/info.json
Executable file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Blend",
|
||||
"description": "Blend the past two image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2. Easier to use interfaces coming soon!",
|
||||
"inputs": {
|
||||
"blend": {
|
||||
"type": "input",
|
||||
"desc": "Function to use to blend the two images.",
|
||||
"default": "function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
module.exports = exports = function(pixels, blur) {
|
||||
let kernel = kernelGenerator(blur,1)
|
||||
kernel = flipKernel(kernel)
|
||||
var oldpix = pixels
|
||||
let kernel = kernelGenerator(blur, 1), oldpix = pixels;
|
||||
kernel = flipKernel(kernel);
|
||||
|
||||
for (let i = 0; i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < pixels.shape[1]; j++) {
|
||||
let neighboutPos = getNeighbouringPixelPositions([i,j])
|
||||
let acc = [0.0,0.0,0.0,0.0]
|
||||
let neighboutPos = getNeighbouringPixelPositions([i, j]);
|
||||
let acc = [0.0, 0.0, 0.0, 0.0];
|
||||
for (let a = 0; a < kernel.length; a++) {
|
||||
for (let b = 0; b < kernel.length; b++) {
|
||||
acc[0] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 0) * kernel[a][b]);
|
||||
@@ -15,12 +14,12 @@ module.exports = exports = function(pixels,blur){
|
||||
acc[3] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 3) * kernel[a][b]);
|
||||
}
|
||||
}
|
||||
pixels.set(i,j,0,acc[0])
|
||||
pixels.set(i,j,1,acc[1])
|
||||
pixels.set(i,j,2,acc[2])
|
||||
pixels.set(i, j, 0, acc[0]);
|
||||
pixels.set(i, j, 1, acc[1]);
|
||||
pixels.set(i, j, 2, acc[2]);
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
return pixels;
|
||||
|
||||
|
||||
|
||||
@@ -56,30 +55,30 @@ module.exports = exports = function(pixels,blur){
|
||||
[5.0 / 159.0, 12.0 / 159.0, 15.0 / 159.0, 12.0 / 159.0, 5.0 / 159.0],
|
||||
[4.0 / 159.0, 9.0 / 159.0, 12.0 / 159.0, 9.0 / 159.0, 4.0 / 159.0],
|
||||
[2.0 / 159.0, 4.0 / 159.0, 5.0 / 159.0, 4.0 / 159.0, 2.0 / 159.0]
|
||||
]
|
||||
];
|
||||
}
|
||||
function getNeighbouringPixelPositions(pixelPosition) {
|
||||
let x = pixelPosition[0],y=pixelPosition[1]
|
||||
let result = []
|
||||
let x = pixelPosition[0], y = pixelPosition[1], result = [];
|
||||
|
||||
for (let i = -2; i <= 2; i++) {
|
||||
let arr = []
|
||||
for(let j=-2;j<=2;j++){
|
||||
arr.push([x + i,y + j])
|
||||
let arr = [];
|
||||
for (let j = -2; j <= 2; j++)
|
||||
arr.push([x + i, y + j]);
|
||||
|
||||
result.push(arr);
|
||||
}
|
||||
result.push(arr)
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
function flipKernel(kernel) {
|
||||
let result = []
|
||||
let result = [];
|
||||
for (let i = kernel.length - 1; i >= 0; i--) {
|
||||
let arr = []
|
||||
let arr = [];
|
||||
for (let j = kernel[i].length - 1; j >= 0; j--) {
|
||||
arr.push(kernel[i][j])
|
||||
arr.push(kernel[i][j]);
|
||||
}
|
||||
result.push(arr)
|
||||
result.push(arr);
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,8 @@
|
||||
* Blur an Image
|
||||
*/
|
||||
module.exports = function Blur(options, UI) {
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
options.blur = options.blur || 2
|
||||
var output;
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
@@ -14,9 +11,6 @@ module.exports = function Blur(options,UI){
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -33,11 +27,6 @@ module.exports = function Blur(options,UI){
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
4
src/modules/Blur/index.js
Normal file
4
src/modules/Blur/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -3,10 +3,7 @@
|
||||
*/
|
||||
|
||||
module.exports = function Brightness(options,UI){
|
||||
options = options || {};
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj){
|
||||
@@ -20,9 +17,6 @@ module.exports = function Brightness(options,UI){
|
||||
For eg. progressObj = new SomeProgressModule()
|
||||
*/
|
||||
|
||||
// Tell the UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a){
|
||||
@@ -39,11 +33,6 @@ module.exports = function Brightness(options,UI){
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
4
src/modules/Brightness/index.js
Normal file
4
src/modules/Brightness/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -3,11 +3,8 @@
|
||||
*/
|
||||
module.exports = function Channel(options, UI) {
|
||||
|
||||
options = options || {};
|
||||
options.channel = options.channel || "green";
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
@@ -15,8 +12,6 @@ module.exports = function Channel(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -30,11 +25,6 @@ module.exports = function Channel(options,UI) {
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step ahs been drawn
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
4
src/modules/Channel/index.js
Normal file
4
src/modules/Channel/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
16
src/modules/Colorbar/Module.js
Normal file
16
src/modules/Colorbar/Module.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = function NdviColormapfunction(options, UI) {
|
||||
|
||||
options.x = options.x || 0;
|
||||
options.y = options.y || 0;
|
||||
options.colormap = options.colormap || "default";
|
||||
options.h = options.h || 10;
|
||||
this.expandSteps([
|
||||
{ 'name': 'gradient', 'options': {} },
|
||||
{ 'name': 'colormap', 'options': { colormap: options.colormap } },
|
||||
{ 'name': 'crop', 'options': { 'y': 0, 'h': options.h } },
|
||||
{ 'name': 'overlay', 'options': { 'x': options.x, 'y': options.y, 'offset': -4 } }
|
||||
]);
|
||||
return {
|
||||
isMeta: true
|
||||
}
|
||||
}
|
||||
4
src/modules/Colorbar/index.js
Normal file
4
src/modules/Colorbar/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
33
src/modules/Colorbar/info.json
Normal file
33
src/modules/Colorbar/info.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "Colorbar",
|
||||
"description": "Generates a colorbar to lay over the image",
|
||||
"inputs": {
|
||||
"colormap": {
|
||||
"type": "select",
|
||||
"desc": "Name of the Colormap",
|
||||
"default": "default",
|
||||
"values": [
|
||||
"default",
|
||||
"greyscale",
|
||||
"stretched",
|
||||
"fastie"
|
||||
]
|
||||
},
|
||||
"x": {
|
||||
"type": "integer",
|
||||
"desc": "X-position of the image on which the new image is overlayed",
|
||||
"default": 0
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"desc": "Y-position of the image on which the new image is overlayed",
|
||||
"default": 0
|
||||
},
|
||||
"h": {
|
||||
"type": "iinteger",
|
||||
"desc": "height of the colorbar",
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"length": 4
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
module.exports = function Colormap(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
@@ -12,8 +8,6 @@ module.exports = function Colormap(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -27,12 +21,6 @@ module.exports = function Colormap(options,UI) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by the UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell the UI that the draw is complete
|
||||
UI.onComplete(options.step);
|
||||
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
|
||||
4
src/modules/Colormap/index.js
Normal file
4
src/modules/Colormap/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -7,8 +7,8 @@ module.exports = function Crop(input,options,callback) {
|
||||
options.y = parseInt(options.y) || 0;
|
||||
|
||||
getPixels(input.src,function(err,pixels){
|
||||
options.w = parseInt(options.w) || Math.floor(0.5*pixels.shape[0]);
|
||||
options.h = parseInt(options.h) || Math.floor(0.5*pixels.shape[1]);
|
||||
options.w = parseInt(options.w) || Math.floor(pixels.shape[0]);
|
||||
options.h = parseInt(options.h) || Math.floor(pixels.shape[1]);
|
||||
var ox = options.x;
|
||||
var oy = options.y;
|
||||
var w = options.w;
|
||||
|
||||
@@ -15,20 +15,21 @@
|
||||
*/
|
||||
module.exports = function CropModule(options, UI) {
|
||||
|
||||
// TODO: we could also set this to {} if nil in AddModule.js to avoid this line:
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
// we should get UI to return the image thumbnail so we can attach our own UI extensions
|
||||
// add our custom in-module html ui:
|
||||
if (options.step.inBrowser) var ui = require('./Ui.js')(options.step, UI);
|
||||
var output,
|
||||
setupComplete = false;
|
||||
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
// save the input image;
|
||||
// TODO: this should be moved to module API to persist the input image
|
||||
options.step.input = input.src;
|
||||
|
||||
require('./Crop')(input, options, function(out, format){
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
@@ -43,6 +44,16 @@
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
|
||||
// we should do this via event/listener:
|
||||
if (ui && ui.hide) ui.hide();
|
||||
|
||||
// start custom UI setup (draggable UI)
|
||||
// only once we have an input image
|
||||
if (setupComplete === false && options.step.inBrowser) {
|
||||
setupComplete = true;
|
||||
ui.setup();
|
||||
}
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
|
||||
|
||||
97
src/modules/Crop/Ui.js
Normal file
97
src/modules/Crop/Ui.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// hide on save
|
||||
module.exports = function CropModuleUi(step, ui) {
|
||||
|
||||
let inputWidth = 0,
|
||||
inputHeight = 0;
|
||||
|
||||
// We don't have input image dimensions at the
|
||||
// time of setting up the UI; that comes when draw() is triggered.
|
||||
// So we trigger setup only on first run of draw()
|
||||
// TODO: link this to an event rather than an explicit call in Module.js
|
||||
function setup() {
|
||||
let x = 0,
|
||||
y = 0;
|
||||
|
||||
// display original uncropped input image on initial setup
|
||||
showOriginal();
|
||||
|
||||
inputWidth = Math.floor(imgEl().naturalWidth);
|
||||
inputHeight = Math.floor(imgEl().naturalHeight);
|
||||
|
||||
// display with 50%/50% default crop:
|
||||
setOptions(x, y, inputWidth, inputHeight);
|
||||
|
||||
$(imgEl()).imgAreaSelect({
|
||||
handles: true,
|
||||
x1: x,
|
||||
y1: y,
|
||||
x2: x + inputWidth / 2,
|
||||
y2: y + inputHeight / 2,
|
||||
// when selection is complete
|
||||
onSelectEnd: function onSelectEnd(img, selection) {
|
||||
// assign crop values to module UI form inputs:
|
||||
let converted = convertToNatural(
|
||||
selection.x1,
|
||||
selection.y1,
|
||||
selection.width,
|
||||
selection.height
|
||||
);
|
||||
setOptions(
|
||||
converted[0],
|
||||
converted[1],
|
||||
converted[2],
|
||||
converted[3]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function convertToNatural(_x, _y, _width, _height) {
|
||||
let displayWidth = $(imgEl()).width(),
|
||||
displayHeight = $(imgEl()).height();
|
||||
// return in same order [ x, y, width, height ]:
|
||||
return [
|
||||
Math.floor(( _x / displayWidth ) * inputWidth),
|
||||
Math.floor(( _y / displayHeight ) * inputHeight),
|
||||
Math.floor(( _width / displayWidth ) * inputWidth),
|
||||
Math.floor(( _height / displayHeight ) * inputHeight)
|
||||
]
|
||||
}
|
||||
|
||||
function remove() {
|
||||
$(imgEl()).imgAreaSelect({
|
||||
remove: true
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
// then hide the draggable UI
|
||||
$(imgEl()).imgAreaSelect({
|
||||
hide: true
|
||||
});
|
||||
}
|
||||
|
||||
// step.imgSelector is not defined, imgElement is:
|
||||
function imgEl() {
|
||||
return step.imgElement;
|
||||
}
|
||||
|
||||
function setOptions(x1, y1, width, height) {
|
||||
let options = $($(imgEl()).parents()[2]).find("input");
|
||||
options[0].value = x1;
|
||||
options[1].value = y1;
|
||||
options[2].value = width;
|
||||
options[3].value = height;
|
||||
}
|
||||
|
||||
// replaces currently displayed output thumbnail with the input image, for ui dragging purposes
|
||||
function showOriginal() {
|
||||
step.imgElement.src = step.input;
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup,
|
||||
remove: remove,
|
||||
hide: hide
|
||||
}
|
||||
}
|
||||
4
src/modules/Crop/index.js
Normal file
4
src/modules/Crop/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -16,12 +16,12 @@
|
||||
"w": {
|
||||
"type": "integer",
|
||||
"desc": "Width of crop",
|
||||
"default": "(50%)"
|
||||
"default": "(100%)"
|
||||
},
|
||||
"h": {
|
||||
"type": "integer",
|
||||
"desc": "Height of crop",
|
||||
"default": "(50%)"
|
||||
"default": "(100%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,6 @@
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
|
||||
var output;
|
||||
var jsQR = require('jsqr');
|
||||
var getPixels = require('get-pixels');
|
||||
@@ -15,8 +10,6 @@ module.exports = function DoNothing(options,UI) {
|
||||
// This function is called everytime a step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
getPixels(input.src,function(err,pixels){
|
||||
@@ -33,13 +26,8 @@ module.exports = function DoNothing(options,UI) {
|
||||
|
||||
// Tell Image Sequencer that this step is complete
|
||||
callback();
|
||||
|
||||
// These values are accessible to the UI
|
||||
options.step.output = input.src;
|
||||
options.step.qrval = decoded;
|
||||
|
||||
// Tell the UI that the step is complete and output is set
|
||||
UI.onComplete(options.step);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
4
src/modules/DecodeQr/index.js
Normal file
4
src/modules/DecodeQr/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -1,9 +1,5 @@
|
||||
module.exports = function Dynamic(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
@@ -12,8 +8,6 @@ module.exports = function Dynamic(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
// start with monochrome, but if options.red, options.green, and options.blue are set, accept them too
|
||||
@@ -66,12 +60,6 @@ module.exports = function Dynamic(options,UI) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by the UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell the UI that the draw is complete
|
||||
UI.onComplete(options.step);
|
||||
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
|
||||
4
src/modules/Dynamic/index.js
Normal file
4
src/modules/Dynamic/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -2,18 +2,14 @@ const _ = require('lodash')
|
||||
|
||||
//define kernels for the sobel filter
|
||||
const kernelx = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]],
|
||||
kernely = [[-1,-2,-1],[0,0,0],[1,2,1]]
|
||||
kernely = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
|
||||
|
||||
let angles = []
|
||||
let mags = []
|
||||
let strongEdgePixels = []
|
||||
let weakEdgePixels = []
|
||||
let notInUI
|
||||
module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio,inBrowser){
|
||||
notInUI = !inBrowser
|
||||
let angles = [], mags = [], strongEdgePixels = [], weakEdgePixels = [], notInUI;
|
||||
module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, inBrowser) {
|
||||
notInUI = !inBrowser;
|
||||
for (var x = 0; x < pixels.shape[0]; x++) {
|
||||
angles.push([])
|
||||
mags.push([])
|
||||
angles.push([]);
|
||||
mags.push([]);
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
var result = changePixel(
|
||||
pixels,
|
||||
@@ -21,25 +17,25 @@ module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio
|
||||
pixels.get(x, y, 3),
|
||||
x,
|
||||
y
|
||||
)
|
||||
let pixel = result.pixel
|
||||
);
|
||||
let pixel = result.pixel;
|
||||
|
||||
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]);
|
||||
|
||||
mags.slice(-1)[0].push(pixel[3])
|
||||
angles.slice(-1)[0].push(result.angle)
|
||||
mags.slice(-1)[0].push(pixel[3]);
|
||||
angles.slice(-1)[0].push(result.angle);
|
||||
}
|
||||
}
|
||||
|
||||
return hysteresis(doubleThreshold(nonMaxSupress(pixels),highThresholdRatio,lowThresholdRatio))
|
||||
return doubleThreshold(nonMaxSupress(pixels), highThresholdRatio, lowThresholdRatio);
|
||||
}
|
||||
|
||||
//changepixel function that convolutes every pixel (sobel filter)
|
||||
function changePixel(pixels, val, a, x, y) {
|
||||
let magX = 0.0
|
||||
let magX = 0.0;
|
||||
for (let a = 0; a < 3; a++) {
|
||||
for (let b = 0; b < 3; b++) {
|
||||
|
||||
@@ -49,7 +45,7 @@ function changePixel(pixels,val,a,x,y){
|
||||
magX += pixels.get(xn, yn, 0) * kernelx[a][b];
|
||||
}
|
||||
}
|
||||
let magY = 0.0
|
||||
let magY = 0.0;
|
||||
for (let a = 0; a < 3; a++) {
|
||||
for (let b = 0; b < 3; b++) {
|
||||
|
||||
@@ -59,95 +55,97 @@ function changePixel(pixels,val,a,x,y){
|
||||
magY += pixels.get(xn, yn, 0) * kernely[a][b];
|
||||
}
|
||||
}
|
||||
let mag = Math.sqrt(Math.pow(magX,2) + Math.pow(magY,2))
|
||||
let angle = Math.atan2(magY,magX)
|
||||
let mag = Math.sqrt(Math.pow(magX, 2) + Math.pow(magY, 2));
|
||||
let angle = Math.atan2(magY, magX);
|
||||
return {
|
||||
pixel:
|
||||
[val, val, val, mag],
|
||||
angle: angle
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//Non Maximum Supression without interpolation
|
||||
function nonMaxSupress(pixels) {
|
||||
angles = angles.map((arr)=>arr.map(convertToDegrees))
|
||||
|
||||
angles = angles.map((arr) => arr.map(convertToDegrees));
|
||||
|
||||
for (let i = 1; i < pixels.shape[0] - 1; i++) {
|
||||
for (let j = 1; j < pixels.shape[1] - 1; j++) {
|
||||
|
||||
let angle = angles[i][j]
|
||||
let pixel = pixels.get(i,j)
|
||||
let angle = angles[i][j];
|
||||
let pixel = pixels.get(i, j);
|
||||
|
||||
if ((angle >= -22.5 && angle <= 22.5) ||
|
||||
(angle < -157.5 && angle >= -180))
|
||||
|
||||
if ((mags[i][j] >= mags[i][j + 1]) &&
|
||||
(mags[i][j] >= mags[i][j - 1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
pixels.set(i, j, 3, mags[i][j]);
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
pixels.set(i, j, 3, 0);
|
||||
|
||||
else if ((angle >= 22.5 && angle <= 67.5) ||
|
||||
(angle < -112.5 && angle >= -157.5))
|
||||
|
||||
if ((mags[i][j] >= mags[i + 1][j + 1]) &&
|
||||
(mags[i][j] >= mags[i - 1][j - 1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
pixels.set(i, j, 3, mags[i][j]);
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
pixels.set(i, j, 3, 0);
|
||||
|
||||
else if ((angle >= 67.5 && angle <= 112.5) ||
|
||||
(angle < -67.5 && angle >= -112.5))
|
||||
|
||||
if ((mags[i][i] >= mags[i + 1][j]) &&
|
||||
(mags[i][j] >= mags[i][j]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
pixels.set(i, j, 3, mags[i][j]);
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
pixels.set(i, j, 3, 0);
|
||||
|
||||
else if ((angle >= 112.5 && angle <= 157.5) ||
|
||||
(angle < -22.5 && angle >= -67.5))
|
||||
|
||||
if ((mags[i][j] >= mags[i + 1][j - 1]) &&
|
||||
(mags[i][j] >= mags[i - 1][j + 1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
pixels.set(i, j, 3, mags[i][j]);
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
pixels.set(i, j, 3, 0);
|
||||
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
return pixels;
|
||||
}
|
||||
//Converts radians to degrees
|
||||
var convertToDegrees = radians => (radians * 180)/Math.PI
|
||||
var convertToDegrees = radians => (radians * 180) / Math.PI;
|
||||
|
||||
//Finds the max value in a 2d array like mags
|
||||
var findMaxInMatrix = arr => Math.max(...arr.map(el=>el.map(val=>!!val?val:0)).map(el=>Math.max(...el)))
|
||||
var findMaxInMatrix = arr => Math.max(...arr.map(el => el.map(val => !!val ? val : 0)).map(el => Math.max(...el)));
|
||||
|
||||
//Applies the double threshold to the image
|
||||
function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio) {
|
||||
const highThreshold = findMaxInMatrix(mags) * 0.2
|
||||
const lowThreshold = highThreshold * lowThresholdRatio
|
||||
|
||||
const highThreshold = findMaxInMatrix(mags) * 0.2;
|
||||
const lowThreshold = highThreshold * lowThresholdRatio;
|
||||
|
||||
for (let i = 0; i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < pixels.shape[1]; j++) {
|
||||
let pixelPos = [i,j]
|
||||
let pixelPos = [i, j];
|
||||
|
||||
mags[i][j] > lowThreshold
|
||||
? mags[i][j] > highThreshold
|
||||
? strongEdgePixels.push(pixelPos)
|
||||
: weakEdgePixels.push(pixelPos)
|
||||
:pixels.set(i,j,3,0)
|
||||
: pixels.set(i, j, 3, 0);
|
||||
}
|
||||
}
|
||||
|
||||
strongEdgePixels.forEach(pix=>pixels.set(pix[0],pix[1],3,255))
|
||||
strongEdgePixels.forEach(pix => pixels.set(pix[0], pix[1], 3, 255));
|
||||
|
||||
return pixels
|
||||
return pixels;
|
||||
}
|
||||
|
||||
// hysteresis edge tracking algorithm
|
||||
function hysteresis(pixels){
|
||||
// hysteresis edge tracking algorithm -- not working as of now
|
||||
/* function hysteresis(pixels) {
|
||||
function getNeighbouringPixelPositions(pixelPosition) {
|
||||
let x = pixelPosition[0], y = pixelPosition[1]
|
||||
return [[x + 1, y + 1],
|
||||
@@ -173,7 +171,7 @@ function hysteresis(pixels){
|
||||
}
|
||||
weakEdgePixels.forEach(pix => pixels.set(pix[0], pix[1], 3, 0))
|
||||
return pixels
|
||||
}
|
||||
} */
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
*/
|
||||
module.exports = function edgeDetect(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
options.highThresholdRatio = options.highThresholdRatio||0.2
|
||||
options.lowThresholdRatio = options.lowThresholdRatio||0.15
|
||||
options.blur = options.blur || 2;
|
||||
options.highThresholdRatio = options.highThresholdRatio||0.2;
|
||||
options.lowThresholdRatio = options.lowThresholdRatio||0.15;
|
||||
|
||||
// Tell UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
@@ -18,16 +15,13 @@ module.exports = function edgeDetect(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
function extraManipulation(pixels){
|
||||
pixels = require('ndarray-gaussian-filter')(pixels,options.blur)
|
||||
return require('./EdgeUtils')(pixels,options.highThresholdRatio,options.lowThresholdRatio,options.inBrowser)
|
||||
pixels = require('ndarray-gaussian-filter')(pixels,options.blur);
|
||||
return require('./EdgeUtils')(pixels,options.highThresholdRatio,options.lowThresholdRatio,options.inBrowser);
|
||||
}
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -39,11 +33,6 @@ module.exports = function edgeDetect(options,UI) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
4
src/modules/EdgeDetect/index.js
Normal file
4
src/modules/EdgeDetect/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -3,18 +3,12 @@
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
var output;
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
require('fisheyegl');
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
if (!options.inBrowser) { // This module is only for browser
|
||||
@@ -59,12 +53,8 @@ module.exports = function DoNothing(options,UI) {
|
||||
// this output is accessible to Image Sequencer
|
||||
step.output = {src: canvas.toDataURL(), format: input.format};
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = step.output.src;
|
||||
|
||||
// Tell Image Sequencer and UI that step has been drawn
|
||||
callback();
|
||||
UI.onComplete(options.step);
|
||||
|
||||
});
|
||||
|
||||
|
||||
4
src/modules/FisheyeGl/index.js
Normal file
4
src/modules/FisheyeGl/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
62
src/modules/Gradient/Module.js
Normal file
62
src/modules/Gradient/Module.js
Normal file
@@ -0,0 +1,62 @@
|
||||
module.exports = function Invert(options, UI) {
|
||||
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input, callback, progressObj) {
|
||||
var getPixels = require('get-pixels');
|
||||
var savePixels = require('save-pixels');
|
||||
|
||||
var step = this;
|
||||
|
||||
getPixels(input.src, function(err, pixels) {
|
||||
|
||||
if (err) {
|
||||
console.log("Bad Image path");
|
||||
return;
|
||||
}
|
||||
var width = 0;
|
||||
|
||||
for (var i = 0; i < pixels.shape[0]; i++) width++;
|
||||
|
||||
for (var i = 0; i < pixels.shape[0]; i++) {
|
||||
for (var j = 0; j < pixels.shape[1]; j++) {
|
||||
let val = (i / width) * 255;
|
||||
pixels.set(i, j, 0, val);
|
||||
pixels.set(i, j, 1, val);
|
||||
pixels.set(i, j, 2, val);
|
||||
pixels.set(i, j, 3, 255);
|
||||
}
|
||||
}
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, input.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/" + input.format + ";base64," + data;
|
||||
output(input.image, datauri, input.format);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
4
src/modules/Gradient/index.js
Normal file
4
src/modules/Gradient/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
5
src/modules/Gradient/info.json
Normal file
5
src/modules/Gradient/info.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Gradient",
|
||||
"description": "Gives a gradient of the image",
|
||||
"inputs": {}
|
||||
}
|
||||
58
src/modules/ImportImage/Module.js
Normal file
58
src/modules/ImportImage/Module.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Import Image module; this fetches a given remote or local image via URL
|
||||
* or data-url, and overwrites the current one. It saves the original as
|
||||
* step.metadata.input for use in future modules such as blending.
|
||||
* TODO: we could accept an operation for blending like "screen" or "overlay",
|
||||
* or a function with blend(r1,g1,b1,a1,r2,g2,b2,a2), OR we could simply allow
|
||||
* subsequent modules to do this blending and keep this one simple.
|
||||
*/
|
||||
module.exports = function ImportImageModule(options, UI) {
|
||||
|
||||
options.imageUrl = options.url || "./images/monarch.png";
|
||||
|
||||
var output,
|
||||
imgObj = new Image();
|
||||
|
||||
// we should get UI to return the image thumbnail so we can attach our own UI extensions
|
||||
|
||||
// add our custom in-module html ui:
|
||||
if (options.step.inBrowser) {
|
||||
var ui = require('./Ui.js')(options.step, UI);
|
||||
ui.setup();
|
||||
}
|
||||
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
var step = this;
|
||||
|
||||
step.metadata = step.metadata || {};
|
||||
// TODO: develop a standard API method for saving each input state,
|
||||
// for reference in future steps (for blending, for example)
|
||||
step.metadata.input = input;
|
||||
|
||||
function onLoad() {
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: imgObj.src,
|
||||
format: options.format
|
||||
}
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
}
|
||||
|
||||
options.format = require('../../util/GetFormat')(options.imageUrl);
|
||||
imgObj.onload = onLoad;
|
||||
imgObj.src = options.imageUrl;
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
54
src/modules/ImportImage/Ui.js
Normal file
54
src/modules/ImportImage/Ui.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// hide on save
|
||||
module.exports = function ImportImageModuleUi(step, ui) {
|
||||
|
||||
function setup(setImage, onLoad) {
|
||||
|
||||
// generate a unique timestamp based id for the dropzone
|
||||
var dropzoneId = 'dropzone-import-image-' + step.ID;
|
||||
|
||||
// add a file input listener
|
||||
var dropZone ='\
|
||||
<div style="padding: 30px;margin: 10px 20% 30px;border: 4px dashed #ccc;border-radius: 8px;text-align: center;color: #444;" id="' + dropzoneId + '">\
|
||||
<p>\
|
||||
<i>Select or drag in an image to overlay.</i>\
|
||||
</p>\
|
||||
<center>\
|
||||
<input type="file" class="file-input" value="">\
|
||||
</center>\
|
||||
</div>';
|
||||
|
||||
// insert into .details area
|
||||
// TODO: develop API-based consistent way to add UI elements
|
||||
$(step.ui)
|
||||
.find('.details')
|
||||
.prepend(dropZone);
|
||||
|
||||
// setup file input listener
|
||||
sequencer.setInputStep({
|
||||
dropZoneSelector: "#" + dropzoneId,
|
||||
fileInputSelector: "#" + dropzoneId + " .file-input",
|
||||
onLoad: function onLoadFromInput(progress) {
|
||||
var reader = progress.target;
|
||||
step.options.imageUrl = reader.result;
|
||||
step.options.url = reader.result;
|
||||
sequencer.run();
|
||||
setUrlHashParameter("steps", sequencer.toString());
|
||||
}
|
||||
});
|
||||
|
||||
$(step.ui)
|
||||
.find('.btn-save').on('click', function onClickSave() {
|
||||
|
||||
var src = $(step.ui)
|
||||
.find('.det input').val();
|
||||
step.options.imageUrl = src;
|
||||
sequencer.run();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup
|
||||
}
|
||||
}
|
||||
4
src/modules/ImportImage/index.js
Normal file
4
src/modules/ImportImage/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
12
src/modules/ImportImage/info.json
Normal file
12
src/modules/ImportImage/info.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Import Image",
|
||||
"description": "Import a new image and replace the original with it. Future versions may enable a blend mode. Specify an image by URL or by file selector.",
|
||||
"url": "https://github.com/publiclab/image-sequencer/tree/master/MODULES.md",
|
||||
"inputs": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"desc": "URL of image to import",
|
||||
"default": "./images/monarch.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
/*
|
||||
* Invert the image
|
||||
*/
|
||||
module.exports = function Invert(options,UI) {
|
||||
function Invert(options, UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
@@ -14,8 +10,6 @@ module.exports = function Invert(options,UI) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
// Tell UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
@@ -28,14 +22,9 @@ module.exports = function Invert(options,UI) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
return input.pixelManipulation({
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
@@ -53,3 +42,10 @@ module.exports = function Invert(options,UI) {
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
var info = {
|
||||
"name": "Invert",
|
||||
"description": "Inverts the image.",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
module.exports = [Invert, info];
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
*/
|
||||
module.exports = function Ndvi(options, UI) {
|
||||
|
||||
options = options || {};
|
||||
if (options.step.inBrowser) var ui = require('./Ui.js')(options.step, UI);
|
||||
|
||||
options.filter = options.filter || "red";
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
@@ -16,8 +15,6 @@ module.exports = function Ndvi(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -32,11 +29,13 @@ module.exports = function Ndvi(options,UI) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by the UI.
|
||||
options.step.output = datauri;
|
||||
}
|
||||
|
||||
// Tell the UI that step has been drawn succesfully.
|
||||
UI.onComplete(options.step);
|
||||
function modifiedCallback() {
|
||||
if (options.step.inBrowser) {
|
||||
ui.setup();
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -45,7 +44,7 @@ module.exports = function Ndvi(options,UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
callback: modifiedCallback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
31
src/modules/Ndvi/Ui.js
Normal file
31
src/modules/Ndvi/Ui.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// hide on save
|
||||
module.exports = function CropModuleUi(step, ui) {
|
||||
|
||||
/* sets the pixel value under the mouse pointer
|
||||
* on the title attribute of the image element.
|
||||
*/
|
||||
function setup() {
|
||||
var ndviImage = $(imgEl());
|
||||
|
||||
ndviImage.mousemove(function ndviMousemove(e) {
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = ndviImage.width();
|
||||
canvas.height = ndviImage.height();
|
||||
canvas.getContext('2d').drawImage(this, 0, 0);
|
||||
|
||||
var offset = $(this).offset();
|
||||
var xPos = e.pageX - offset.left;
|
||||
var yPos = e.pageY - offset.top;
|
||||
ndviImage[0].title = "NDVI: " + canvas.getContext('2d').getImageData(xPos, yPos, 1, 1).data[0];
|
||||
});
|
||||
}
|
||||
// step.imgSelector is not defined, imgElement is:
|
||||
function imgEl() {
|
||||
return step.imgElement;
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup
|
||||
}
|
||||
}
|
||||
4
src/modules/Ndvi/index.js
Normal file
4
src/modules/Ndvi/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
9
src/modules/NdviColormap/Module.js
Normal file
9
src/modules/NdviColormap/Module.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Sample Meta Module for demonstration purpose only
|
||||
*/
|
||||
module.exports = function NdviColormapfunction() {
|
||||
this.expandSteps([{ 'name': 'ndvi', 'options': {} }, { 'name': 'colormap', options: {} }]);
|
||||
return {
|
||||
isMeta: true
|
||||
}
|
||||
}
|
||||
4
src/modules/NdviColormap/index.js
Normal file
4
src/modules/NdviColormap/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
6
src/modules/NdviColormap/info.json
Normal file
6
src/modules/NdviColormap/info.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "NDVI-Colormap",
|
||||
"description": "Sequentially Applies NDVI and Colormap steps",
|
||||
"inputs": {},
|
||||
"length": 2
|
||||
}
|
||||
70
src/modules/Overlay/Module.js
Normal file
70
src/modules/Overlay/Module.js
Normal file
@@ -0,0 +1,70 @@
|
||||
module.exports = function Dynamic(options, UI, util) {
|
||||
|
||||
options.x = options.x || 0;
|
||||
options.y = options.y || 0;
|
||||
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
options.offset = options.offset || -2;
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
var step = this;
|
||||
|
||||
// save the pixels of the base image
|
||||
var baseStepImage = this.getStep(options.offset).image;
|
||||
var baseStepOutput = this.getOutput(options.offset);
|
||||
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
getPixels(input.src, function(err, pixels) {
|
||||
options.secondImagePixels = pixels;
|
||||
|
||||
function changePixel(r1, g1, b1, a1, x, y) {
|
||||
|
||||
// overlay
|
||||
var p = options.secondImagePixels;
|
||||
if (x >= options.x
|
||||
&& x < p.shape[0]
|
||||
&& y >= options.y
|
||||
&& y < p.shape[1])
|
||||
return [
|
||||
p.get(x, y, 0),
|
||||
p.get(x, y, 1),
|
||||
p.get(x, y, 2),
|
||||
p.get(x, y, 3)
|
||||
];
|
||||
else
|
||||
return [r1, g1, b1, a1];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
// run PixelManipulation on first Image pixels
|
||||
return require('../_nomodule/PixelManipulation.js')(baseStepOutput, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: baseStepOutput.format,
|
||||
image: baseStepImage,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
4
src/modules/Overlay/index.js
Normal file
4
src/modules/Overlay/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
21
src/modules/Overlay/info.json
Normal file
21
src/modules/Overlay/info.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Overlay",
|
||||
"description": "Overlays an Image over another at a given position(x,y)",
|
||||
"inputs": {
|
||||
"x": {
|
||||
"type": "integer",
|
||||
"desc": "X-position of the image on which the new image is overlayed",
|
||||
"default": 0
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"desc": "Y-position of the image on which the new image is overlayed",
|
||||
"default": 0
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"desc": "offset to the output of the step on which the output of the last step is overlayed",
|
||||
"default": -2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
*/
|
||||
module.exports = function Saturation(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj) {
|
||||
@@ -14,8 +10,6 @@ module.exports = function Saturation(options,UI) {
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -39,11 +33,6 @@ module.exports = function Saturation(options,UI) {
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step ahs been drawn
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
4
src/modules/Saturation/index.js
Normal file
4
src/modules/Saturation/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
]
|
||||
@@ -4,51 +4,63 @@
|
||||
*/
|
||||
module.exports = function PixelManipulation(image, options) {
|
||||
|
||||
options = options || {};
|
||||
options.changePixel = options.changePixel || function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
};
|
||||
options.extraManipulation = options.extraManipulation||function extraManipulation(pixels){
|
||||
return pixels;
|
||||
// To handle the case where pixelmanipulation is called on the input object itself
|
||||
// like input.pixelManipulation(options)
|
||||
if(arguments.length <= 1){
|
||||
options = image;
|
||||
image = this;
|
||||
}
|
||||
|
||||
var getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
options = options || {};
|
||||
options.changePixel =
|
||||
options.changePixel ||
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
};
|
||||
|
||||
//
|
||||
options.extraManipulation =
|
||||
options.extraManipulation ||
|
||||
function extraManipulation(pixels) {
|
||||
return pixels;
|
||||
};
|
||||
|
||||
var getPixels = require("get-pixels"),
|
||||
savePixels = require("save-pixels");
|
||||
|
||||
getPixels(image.src, function (err, pixels) {
|
||||
|
||||
if (err) {
|
||||
console.log('Bad image path', image);
|
||||
console.log("Bad image path", image);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.getNeighbourPixel) {
|
||||
options.getNeighbourPixel.fun = function (distX,distY) {
|
||||
options.getNeighbourPixel.fun = function getNeighborPixel(distX, distY) {
|
||||
return options.getNeighbourPixel(pixels, x, y, distX, distY);
|
||||
};
|
||||
}
|
||||
|
||||
// iterate through pixels;
|
||||
// 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
|
||||
|
||||
if(!options.inBrowser){
|
||||
if (!options.inBrowser && !process.env.TEST) {
|
||||
try {
|
||||
var pace = require('pace')((pixels.shape[0] * pixels.shape[1]));
|
||||
}
|
||||
catch(e){
|
||||
var pace = require("pace")(pixels.shape[0] * pixels.shape[1]);
|
||||
} catch (e) {
|
||||
options.inBrowser = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (var x = 0; x < pixels.shape[0]; x++) {
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
|
||||
var pixel = options.changePixel(
|
||||
pixels.get(x, y, 0),
|
||||
pixels.get(x, y, 1),
|
||||
pixels.get(x, y, 2),
|
||||
pixels.get(x, y, 3)
|
||||
pixels.get(x, y, 3),
|
||||
x,
|
||||
y
|
||||
);
|
||||
|
||||
pixels.set(x, y, 0, pixel[0]);
|
||||
@@ -56,13 +68,12 @@ module.exports = function PixelManipulation(image, options) {
|
||||
pixels.set(x, y, 2, pixel[2]);
|
||||
pixels.set(x, y, 3, pixel[3]);
|
||||
|
||||
if(!options.inBrowser)
|
||||
pace.op()
|
||||
if (!options.inBrowser && !process.env.TEST) pace.op();
|
||||
}
|
||||
}
|
||||
|
||||
if(options.extraManipulation)
|
||||
pixels = options.extraManipulation(pixels)
|
||||
// perform any extra operations on the entire array:
|
||||
if (options.extraManipulation) pixels = options.extraManipulation(pixels);
|
||||
|
||||
// there may be a more efficient means to encode an image object,
|
||||
// but node modules and their documentation are essentially arcane on this point
|
||||
@@ -70,15 +81,16 @@ module.exports = function PixelManipulation(image, options) {
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format, { quality: 100 });
|
||||
|
||||
r.on('data', function(chunk){
|
||||
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);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
50
src/ui/SetInputStep.js
Normal file
50
src/ui/SetInputStep.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// TODO: potentially move this into ImportImage module
|
||||
function setInputStepInit() {
|
||||
|
||||
return function setInputStep(options) {
|
||||
|
||||
var dropzone = $(options.dropZoneSelector);
|
||||
var fileInput = $(options.fileInputSelector);
|
||||
|
||||
var onLoad = options.onLoad;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
function handleFile(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // stops the browser from redirecting.
|
||||
|
||||
if (e.target && e.target.files) var file = e.target.files[0];
|
||||
else var file = e.dataTransfer.files[0];
|
||||
if(!file) return;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = onLoad;
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
fileInput.on('change', handleFile);
|
||||
|
||||
dropzone[0].addEventListener('drop', handleFile, false);
|
||||
|
||||
dropzone.on('dragover', function onDragover(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
}, false);
|
||||
|
||||
dropzone.on('dragenter', function onDragEnter(e) {
|
||||
dropzone.addClass('hover');
|
||||
});
|
||||
|
||||
dropzone.on('dragleave', function onDragLeave(e) {
|
||||
dropzone.removeClass('hover');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = setInputStepInit;
|
||||
@@ -7,12 +7,10 @@ module.exports = function UserInterface(events = {}) {
|
||||
events.onSetup = events.onSetup || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
} else if(step.inBrowser) {
|
||||
// Create and append an HTML Element
|
||||
console.log("Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Create a NodeJS Object
|
||||
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
}
|
||||
@@ -21,12 +19,10 @@ module.exports = function UserInterface(events = {}) {
|
||||
events.onDraw = events.onDraw || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
} else if(step.inBrowser) {
|
||||
// Overlay a loading spinner
|
||||
console.log("Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Don't do anything
|
||||
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
@@ -35,13 +31,11 @@ module.exports = function UserInterface(events = {}) {
|
||||
events.onComplete = events.onComplete || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
} else if(step.inBrowser) {
|
||||
// Update the DIV Element
|
||||
// Hide the laoding spinner
|
||||
console.log("Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Update the NodeJS Object
|
||||
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
@@ -50,12 +44,10 @@ module.exports = function UserInterface(events = {}) {
|
||||
events.onRemove = events.onRemove || function(step) {
|
||||
if (step.ui == false){
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
} else if(step.inBrowser) {
|
||||
// Remove the DIV Element
|
||||
console.log("Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Delete the NodeJS Object
|
||||
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
}
|
||||
|
||||
7
src/ui/prepareDynamic.js
Normal file
7
src/ui/prepareDynamic.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var sequencer = [];
|
||||
var module = {
|
||||
exports: {},
|
||||
set exports(val){
|
||||
sequencer.push(val);
|
||||
}
|
||||
}
|
||||
40
src/util/GetFormat.js
Normal file
40
src/util/GetFormat.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Determine format from a URL or data-url, return "jpg" "png" "gif" etc
|
||||
* TODO: write a test for this using the examples
|
||||
*/
|
||||
module.exports = function GetFormat(src) {
|
||||
|
||||
var format = undefined; // haha default
|
||||
|
||||
// EXAMPLE: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
// EXAMPLE: "http://example.com/example.png"
|
||||
// EXAMPLE: "/example.png"
|
||||
|
||||
if (isDataUrl(src)) {
|
||||
format = src.split(';')[0].split('/').pop();
|
||||
} else {
|
||||
format = src.split('.').pop();
|
||||
}
|
||||
|
||||
function isDataUrl(src) {
|
||||
return src.substr(0, 10) === "data:image"
|
||||
}
|
||||
|
||||
format = format.toLowerCase();
|
||||
|
||||
if (format === "jpeg") format = "jpg";
|
||||
|
||||
function validateFormat(data){
|
||||
let supportedFormats = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'canvas',
|
||||
];
|
||||
return supportedFormats.includes(data);
|
||||
}
|
||||
|
||||
return validateFormat(format)?format:'jpg';
|
||||
|
||||
}
|
||||
49
src/util/getStep.js
Normal file
49
src/util/getStep.js
Normal file
@@ -0,0 +1,49 @@
|
||||
module.exports = {
|
||||
getPreviousStep: function() {
|
||||
return this.getStep(-1);
|
||||
},
|
||||
|
||||
getNextStep: function() {
|
||||
return this.getStep(1);
|
||||
},
|
||||
|
||||
getInput: function(offset) {
|
||||
if (offset + this.getIndex() === 0) offset++;
|
||||
return this.getStep(offset - 1).output;
|
||||
},
|
||||
|
||||
getOutput: function(offset) {
|
||||
return this.getStep(offset).output;
|
||||
},
|
||||
|
||||
getOptions: function() {
|
||||
return this.getStep(0).options;
|
||||
},
|
||||
|
||||
setOptions: function(optionsObj) {
|
||||
let options = this.getStep(0).options;
|
||||
for (let key in optionsObj) {
|
||||
if (options[key]) options[key] = optionsObj[key];
|
||||
}
|
||||
},
|
||||
|
||||
getFormat: function() {
|
||||
return this.getStep(-1).output.format;
|
||||
},
|
||||
|
||||
getHeight: function(callback) {
|
||||
let img = new Image();
|
||||
img.onload = function() {
|
||||
callback(img.height);
|
||||
};
|
||||
img.src = this.getInput(0).src;
|
||||
},
|
||||
|
||||
getWidth: function(callback) {
|
||||
let img = new Image();
|
||||
img.onload = function() {
|
||||
callback(img.width);
|
||||
};
|
||||
img.src = this.getInput(0).src;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ const test = require('tape');
|
||||
test('Output directory is correctly generated',function(t){
|
||||
cliUtils.makedir('./output/',function(){
|
||||
require('fs').access('./output/.',function(err){
|
||||
t.true(!err,"Access the created dir")
|
||||
t.end()
|
||||
t.true(!err,"Access the created dir");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
19
test/modules/dynamic-imports.js
Normal file
19
test/modules/dynamic-imports.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var test = require('tape');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
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) {
|
||||
sequencer.loadNewModule('test', { func: require('./testModule/Module'), info: require('./testModule/info') });
|
||||
sequencer.loadImages('image1', red);
|
||||
t.deepEqual(sequencer.modules['test'], [
|
||||
require('./testModule/Module'),
|
||||
require('./testModule/info')
|
||||
], "test module was succesfully imported");
|
||||
sequencer.addSteps('invert');
|
||||
sequencer.addSteps('test');
|
||||
sequencer.addSteps('invert');
|
||||
sequencer.run();
|
||||
t.end();
|
||||
});
|
||||
@@ -15,13 +15,12 @@ var sequencer = ImageSequencer({ ui: false });
|
||||
var qr = require('./images/IS-QR.js');
|
||||
var test_png = require('./images/test.png.js');
|
||||
var test_gif = require('./images/test.gif.js');
|
||||
var spinner = require('ora')('').start()
|
||||
|
||||
sequencer.loadImages(test_png);
|
||||
sequencer.addSteps(['invert', 'invert']);
|
||||
|
||||
test("Preload", function(t) {
|
||||
sequencer.run(spinner,function(){
|
||||
sequencer.run({ mode: 'test' }, function() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@@ -52,7 +51,7 @@ test("Twice inverted image is identical to original image", function (t) {
|
||||
|
||||
test("Decode QR module works properly :: setup", function(t) {
|
||||
sequencer.loadImage(qr, function() {
|
||||
this.addSteps('decode-qr').run(spinner.start(),function(){
|
||||
this.addSteps('decode-qr').run({ mode: 'test' }, function() {
|
||||
t.end();
|
||||
});
|
||||
})
|
||||
@@ -65,7 +64,7 @@ test("Decode QR module works properly :: teardown", function (t) {
|
||||
|
||||
test("PixelManipulation works for PNG images", function(t) {
|
||||
sequencer.loadImages(test_png, function() {
|
||||
this.addSteps('invert').run(spinner.start(),function(out){
|
||||
this.addSteps('invert').run({ mode: 'test' }, function(out) {
|
||||
t.equal(1, 1)
|
||||
t.end();
|
||||
});
|
||||
@@ -74,10 +73,9 @@ test("PixelManipulation works for PNG images", function (t) {
|
||||
|
||||
test("PixelManipulation works for GIF images", function(t) {
|
||||
sequencer.loadImages(test_gif, function() {
|
||||
this.addSteps('invert').run(spinner,function(out){
|
||||
this.addSteps('invert').run({ mode: 'test' }, function(out) {
|
||||
t.equal(1, 1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
spinner.stop(true)
|
||||
|
||||
@@ -164,11 +164,27 @@ test('insertSteps({image: {index: index, name: "module", o: options} }) inserts
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
test('run() runs the sequencer and returns output to callback', function(t) {
|
||||
sequencer.run('test',function(out){
|
||||
sequencer.run({ mode: 'test' }, function(out) {
|
||||
t.equal(typeof (sequencer.images.test.steps[sequencer.images.test.steps.length - 1].output), "object", "Output is Generated");
|
||||
t.equal(out, sequencer.images.test.steps[sequencer.images.test.steps.length - 1].output.src, "Output callback works");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('getStep(offset) returns the step at offset distance relative to current step', function(t) {
|
||||
sequencer.addSteps('test', 'invert', {});
|
||||
sequencer.addSteps('test', 'blend', {});
|
||||
sequencer.run({ mode: 'test' }, function(out) {
|
||||
t.equal(!!out, true, "Blend generates output");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('toCliString() returns the CLI command for the sequence', function(t) {
|
||||
t.deepEqual(sequencer.toCliString(), `sequencer -i [PATH] -s "channel channel channel channel channel invert blend" -d '{"channel":"green"}'`, "works correctly");
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
58
test/modules/import-export.js
Normal file
58
test/modules/import-export.js
Normal file
@@ -0,0 +1,58 @@
|
||||
var test = require('tape');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
test('toString() and stepToString() return the step/steps in string format', function(t) {
|
||||
sequencer.loadImages('image1', red);
|
||||
sequencer.addSteps('channel');
|
||||
sequencer.addSteps('invert');
|
||||
t.equal(sequencer.toString(), "channel{channel:green},invert{}", "toString works");
|
||||
t.equal(sequencer.stepToString(sequencer.steps[1]), "channel{channel:green}", "stepToString works");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('stringToJSON() and stringToJSONstep() return the step/steps from a string', function(t) {
|
||||
t.deepEqual(sequencer.stringToJSON('channel{channel:green},invert{},crop{x:0|y:0|w:50%25|h:50%25},blend{blend:function(r1%2C%20g1%2C%20b1%2C%20a1%2C%20r2%2C%20g2%2C%20b2%2C%20a2)%20%7B%20return%20%5B%20r1%2C%20g2%2C%20b2%2C%20a2%20%5D%20%7D}'), [
|
||||
{ name: 'channel', options: { channel: 'green' } },
|
||||
{ name: 'invert', options: {} },
|
||||
{ name: 'crop', options: { x: '0', y: '0', w: '50%', h: '50%' } },
|
||||
{ name: 'blend', options: { blend: 'function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }' } }
|
||||
]);
|
||||
t.deepEqual(sequencer.stringToJSONstep("channel{channel:green}"), { name: 'channel', options: { channel: 'green' } });
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('stringToJSON() and stringToJSONstep() return the step/steps from a string without configs, using defaults', function(t) {
|
||||
t.deepEqual(sequencer.stringToJSON('channel,blur,invert,blend'), [
|
||||
{ name: 'channel', options: {} },
|
||||
{ name: 'blur', options: {} },
|
||||
{ name: 'invert', options: {} },
|
||||
{ name: 'blend', options: {} }
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('toJSON() returns the right sequence of steps', function(t) {
|
||||
t.deepEqual(sequencer.toJSON(), [
|
||||
{ name: 'channel', options: { channel: 'green' } },
|
||||
{ name: 'invert', options: {} }
|
||||
]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('importString() imports a string of steps into the sequencer', function(t) {
|
||||
sequencer.importString('brightness{brightness:50},invert');
|
||||
t.equal(sequencer.toString(), "channel{channel:green},invert{},brightness{brightness:50},invert{}");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('importJSON() imports a JSON array of steps into the sequencer', function(t) {
|
||||
sequencer.importJSON([
|
||||
{ name: 'blur', options: {} }
|
||||
]);
|
||||
t.equal(sequencer.toString(), "channel{channel:green},invert{},brightness{brightness:50},invert{},blur{blur:2}")
|
||||
t.end();
|
||||
});
|
||||
|
||||
16
test/modules/meta-modules.js
Normal file
16
test/modules/meta-modules.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var test = require('tape');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
var sequencer1 = ImageSequencer({ ui: false });
|
||||
var sequencer2 = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
test('Load ndvi-colormap meta module', function(t) {
|
||||
sequencer1.loadImages('image1', red);
|
||||
sequencer2.loadImages('image1', red);
|
||||
sequencer1.addSteps('ndvi-colormap');
|
||||
sequencer2.addSteps(['ndvi', 'colormap']);
|
||||
t.equal(sequencer1.images.image1.steps[0].options.name, sequencer2.steps[0].options.name, "First step OK");
|
||||
t.equal(sequencer1.images.image1.steps[1].options.name, sequencer2.steps[1].options.name, "Second step OK");
|
||||
t.end();
|
||||
});
|
||||
29
test/modules/run.js
Normal file
29
test/modules/run.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var test = require('tape');
|
||||
var DataURItoBuffer = require('data-uri-to-buffer');
|
||||
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
sequencer.loadImages('image1', red);
|
||||
sequencer.addSteps('invert');
|
||||
sequencer.addSteps('invert');
|
||||
sequencer.addSteps('invert');
|
||||
|
||||
test('run() works with all possible argument combinations',function(t){
|
||||
sequencer.run(function (out) {
|
||||
var output1 = DataURItoBuffer(sequencer.images.image1.steps.slice(-1)[0].output.src);
|
||||
sequencer.images.image1.steps.splice(1,1);
|
||||
sequencer.run({index: 1},function(out){
|
||||
var output2 = DataURItoBuffer(sequencer.images.image1.steps.slice(-1)[0].output.src);
|
||||
t.deepEqual(output1,output2,"output remains same after removing a step and running sequencer from a greater index");
|
||||
sequencer.run(function(out){
|
||||
t.end();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
33
test/modules/testModule/Module.js
Normal file
33
test/modules/testModule/Module.js
Normal file
@@ -0,0 +1,33 @@
|
||||
module.exports = function Dynamic(options, UI, util) {
|
||||
// Tests for functions that are available inside modules only
|
||||
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input, callback) {
|
||||
|
||||
var step = this;
|
||||
|
||||
if (this.getPreviousStep().options.name != "invert") throw new Error("getPreviousStep not working");
|
||||
if (this.getNextStep().options.name != "invert") throw new Error("getNextStep not working");
|
||||
if (this.getInput(0) != this.getOutput(-1)) throw new Error("getInput and getOuput now working")
|
||||
if (this.getFormat() != "jpeg") throw new Error("getFormat not working");
|
||||
if (this.getOptions().name != "test") throw new Error("getOptions not working");
|
||||
this.setOptions({ name: "test-1" });
|
||||
if (this.getOptions().name != "test-1") throw new Error("setOptions not working not working");
|
||||
this.getHeight(((h) => { if (h != 16) throw new Error("getHeight not working") }));
|
||||
this.getWidth((w) => { if (w != 16) throw new Error("getWidth not working") });
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
5
test/modules/testModule/info.json
Normal file
5
test/modules/testModule/info.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "test",
|
||||
"description": "Functions that are scoped inside modules are tested with this. This Module is dynamically loaded.",
|
||||
"inputs": {}
|
||||
}
|
||||
Reference in New Issue
Block a user