mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-06 16:30:01 +01:00
Compare commits
36 Commits
ndvi-expla
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de20254637 | ||
|
|
10c6ad5865 | ||
|
|
a0f7e6c766 | ||
|
|
2b07ecdb36 | ||
|
|
37122d4c28 | ||
|
|
871453b857 | ||
|
|
ac183205f0 | ||
|
|
be810fb9c4 | ||
|
|
8d3ca0887f | ||
|
|
1de72d7325 | ||
|
|
58a4798674 | ||
|
|
f8006da07e | ||
|
|
09c4f250a2 | ||
|
|
bd490d2515 | ||
|
|
ca79924d11 | ||
|
|
630eb773de | ||
|
|
6ce78f87f4 | ||
|
|
991e9bb29c | ||
|
|
cbbfbff4bc | ||
|
|
768e2ce95d | ||
|
|
51524f747a | ||
|
|
b16b45afa8 | ||
|
|
39418da187 | ||
|
|
8547c60873 | ||
|
|
adc21ae843 | ||
|
|
2abf0fae09 | ||
|
|
711ae62ee3 | ||
|
|
4d75fb9640 | ||
|
|
5ac3ef008d | ||
|
|
98f913b32e | ||
|
|
c2756ffdbb | ||
|
|
d887f5eb61 | ||
|
|
3b791ad58b | ||
|
|
fba80bb151 | ||
|
|
0ceb36ffde | ||
|
|
edaa8895c7 |
109
CONTRIBUTING.md
109
CONTRIBUTING.md
@@ -12,6 +12,7 @@ Most contribution (we imagine) would be in the form of API-compatible modules, w
|
||||
* [Info File](#info-file)
|
||||
* [Ideas](#ideas)
|
||||
|
||||
****
|
||||
|
||||
## Contribution ideas
|
||||
|
||||
@@ -27,24 +28,30 @@ If you find a bug please list it here, and help us develop Image Sequencer by [o
|
||||
|
||||
Most contributions can happen in modules, rather than to core library code. Modules and their [corresponding info files](#info-file) are included into the library in this file: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
|
||||
|
||||
Module names, descriptions, and parameters are set in the `info.json` file -- [see below](#info-file).
|
||||
|
||||
Any module must follow this basic format:
|
||||
|
||||
```js
|
||||
module.exports = function ModuleName(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Title of the Module";
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var output = /*do something with the input*/ ;
|
||||
UI.onDraw(options.step); // tell the UI to "draw"
|
||||
|
||||
this.output = output;
|
||||
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);
|
||||
UI.onComplete(options.step); // tell UI we are done
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -56,6 +63,7 @@ module.exports = function ModuleName(options,UI) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### options
|
||||
|
||||
The object `options` stores some important information. This is how you can accept
|
||||
@@ -67,6 +75,19 @@ whether your module is being run on a browser.
|
||||
|
||||
### draw()
|
||||
|
||||
To add a module to Image Sequencer, it must have a `draw` method; you can wrap an existing module to add them:
|
||||
|
||||
* `module.draw(input, callback)`
|
||||
|
||||
The `draw` method should accept an `input` parameter, which will be an object of the form:
|
||||
|
||||
```js
|
||||
input = {
|
||||
src: "datauri of an image here",
|
||||
format: "jpeg/png/etc"
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -82,17 +103,17 @@ constant definitions must be done **outside** the `draw()` method's definition.
|
||||
format: "<png|jpeg|gif>"
|
||||
}
|
||||
```
|
||||
* `callback` is a function which is responsible to tell the sequencer that the
|
||||
step has been "drawn".
|
||||
* `callback` is a function which is responsible to tell the sequencer that the step has been "drawn".
|
||||
|
||||
When you have done your calculations and produced an image output, you are required
|
||||
to set `this.output` to an object similar to what the input object was, call
|
||||
`callback()`, and set `options.step.output` equal to the output DataURL
|
||||
When you have done your calculations and produced an image output, you are required to set `this.output` to an object similar to what the input object was, call `callback()`, and set `options.step.output` equal to the output DataURL
|
||||
|
||||
* `progressObj` is an optional additional Object that can be passed in the format `draw(input, callback, progressObj)`, which handles the progress output; see [Progress reporting](#progress-reporting) below.
|
||||
|
||||
### UI Methods
|
||||
|
||||
The module is responsible for emitting various events for the UI to capture. There are
|
||||
four events in all:
|
||||
The module is responsible for emitting various events for the UI to capture.
|
||||
|
||||
There are four events in all:
|
||||
|
||||
* `UI.onSetup(options.step)` must be emitted when the module is added. So it must be emitted outside the draw method's definition as shown above.
|
||||
* `UI.onDraw(options.step)` must be emitted whenever the `draw()` method is called. So it should ideally be the first line of the definition of the `draw` method.
|
||||
@@ -100,26 +121,14 @@ four events in all:
|
||||
is ready. An argument, that is the DataURL of the output image must be passed in.
|
||||
* `UI.onRemove(options.step)` is emitted automatically and the module should not emit it.
|
||||
|
||||
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
|
||||
### Name and description
|
||||
|
||||
* `module.draw()`
|
||||
|
||||
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
|
||||
|
||||
```js
|
||||
input = {
|
||||
src: "datauri here",
|
||||
format: "jpeg/png/etc"
|
||||
}
|
||||
```
|
||||
|
||||
### options.title
|
||||
|
||||
For display in the web-based UI, each module may also have a title `options.title`.
|
||||
For display in the web-based demo UI, set the `name` and `description` fields in the `info.json` file for the module.
|
||||
|
||||
## Info file
|
||||
|
||||
All module folders must have an `info.json` file which looks like the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Name of Module to be displayed",
|
||||
@@ -155,6 +164,52 @@ Similarly, "Select" type inputs should have a `values` array.
|
||||
|
||||
Also, A module may have output values. These must be defined as shown above.
|
||||
|
||||
### Progress reporting
|
||||
|
||||
The default "loading spinner" can be optionally overriden with a custom progress object to draw progress on the CLI, following is a basic module format for the same:
|
||||
|
||||
```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
|
||||
|
||||
/* 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 {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `progressObj` parameter of `draw()` is not consumed unless a custom progress bar needs to be drawn, for which this default spinner should be stopped with `progressObj.stop()` and image-sequencer is informed about the custom progress bar with `progressObj.overrideFlag = true;` following which this object can be overriden with custom progress object.
|
||||
|
||||
### Module example
|
||||
|
||||
See existing module `green-channel` for an example: https://github.com/publiclab/image-sequencer/tree/master/src/modules/GreenChannel/Module.js
|
||||
|
||||
80
Gruntfile.js
80
Gruntfile.js
@@ -1,50 +1,50 @@
|
||||
module.exports = function(grunt) {
|
||||
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"),
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
watch: {
|
||||
options : {
|
||||
livereload: true
|
||||
},
|
||||
source: {
|
||||
files: [
|
||||
'src/*.js',
|
||||
'src/*/*.js',
|
||||
'Gruntfile.js'
|
||||
],
|
||||
tasks: [ 'build:js' ]
|
||||
}
|
||||
watch: {
|
||||
options: {
|
||||
livereload: true
|
||||
},
|
||||
source: {
|
||||
files: ["src/*.js", "src/*/*.js", "src/*/*/*.js", "Gruntfile.js"],
|
||||
tasks: ["build:js"]
|
||||
}
|
||||
},
|
||||
|
||||
browserify: {
|
||||
dist: {
|
||||
src: ['src/ImageSequencer.js'],
|
||||
dest: 'dist/image-sequencer.js'
|
||||
}
|
||||
},
|
||||
browserify: {
|
||||
dist: {
|
||||
src: ["src/ImageSequencer.js"],
|
||||
dest: "dist/image-sequencer.js"
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
dist: {
|
||||
src: ['./dist/image-sequencer.js'],
|
||||
dest: './dist/image-sequencer.min.js'
|
||||
uglify: {
|
||||
dist: {
|
||||
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'
|
||||
]);
|
||||
|
||||
/* Default (development): Watch files and build on change. */
|
||||
grunt.registerTask("default", ["watch"]);
|
||||
grunt.registerTask("build", ["browserify:dist", "uglify:dist"]);
|
||||
grunt.registerTask("serve", ["browserify:dist", "uglify:dist", "browserSync", "watch"]);
|
||||
};
|
||||
|
||||
28
README.md
28
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
|
||||
|
||||
@@ -204,13 +204,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)
|
||||
|
||||
14471
dist/image-sequencer.js
vendored
14471
dist/image-sequencer.js
vendored
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
233
examples/demo.js
233
examples/demo.js
@@ -1,225 +1,38 @@
|
||||
window.onload = function() {
|
||||
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
// Load information of all modules (Name, Inputs, Outputs)
|
||||
var modulesInfo = sequencer.modulesInfo();
|
||||
|
||||
// Add modules to the addStep dropdown
|
||||
for(var m in modulesInfo) {
|
||||
$('#addStep select').append(
|
||||
'<option value="'+m+'">'+modulesInfo[m].name+'</option>'
|
||||
for (var m in modulesInfo) {
|
||||
$("#addStep select").append(
|
||||
'<option value="' + m + '">' + modulesInfo[m].name + "</option>"
|
||||
);
|
||||
}
|
||||
|
||||
// Initial definitions
|
||||
var steps = document.querySelector('#steps');
|
||||
var parser = new DOMParser();
|
||||
var reader = new FileReader();
|
||||
// UI for each step:
|
||||
sequencer.setUI(DefaultHtmlStepUi(sequencer));
|
||||
|
||||
// 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({
|
||||
// UI for the overall demo:
|
||||
var ui = DefaultHtmlSequencerUi(sequencer);
|
||||
|
||||
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>\
|
||||
<img alt="" class="img-thumbnail"/>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn-xs 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.imgElement = step.ui.querySelector('img');
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
var io = Object.assign(inputs, outputs);
|
||||
for (var i in io) {
|
||||
var isInput = inputs.hasOwnProperty(i);
|
||||
var ioUI = "";
|
||||
var inputDesc = (isInput)?inputs[i]:{};
|
||||
if (!isInput) {
|
||||
ioUI += "<span class=\"output\"></span>";
|
||||
}
|
||||
else if (inputDesc.type.toLowerCase() == "select") {
|
||||
ioUI += "<select class=\"form-control\" name=\""+i+"\">";
|
||||
for (var option in inputDesc.values) {
|
||||
ioUI += "<option>"+inputDesc.values[option]+"</option>";
|
||||
}
|
||||
ioUI += "</select>";
|
||||
}
|
||||
else {
|
||||
ioUI = "<input class=\"form-control\" type=\""+inputDesc.type+"\" name=\""+i+"\">";
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
div.className = "row";
|
||||
div.setAttribute('name', i);
|
||||
div.innerHTML = "<div class='det'>\
|
||||
<label for='" + i + "'>" + i + "</label>\
|
||||
"+ioUI+"\
|
||||
</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>");
|
||||
|
||||
// on clicking Save in the details pane of the step
|
||||
$(step.ui.querySelector('div.details .btn-save')).click(function saveOptions() {
|
||||
$(step.ui.querySelector('div.details')).find('input,select').each(function(i, input) {
|
||||
step.options[$(input).attr('name')] = input.value;
|
||||
});
|
||||
sequencer.run();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// File handling
|
||||
|
||||
$('#addStep select').on('change', selectNewStepUI);
|
||||
|
||||
function selectNewStepUI() {
|
||||
$('#options').html('');
|
||||
var m = $('#addStep select').val();
|
||||
for(var input in modulesInfo[m].inputs) {
|
||||
var inputUI = "";
|
||||
var inputDesc = modulesInfo[m].inputs[input];
|
||||
if (inputDesc.type.toLowerCase() == "select") {
|
||||
inputUI += "<select class=\"form-control\" name=\""+input+"\">";
|
||||
for (var option in inputDesc.values) {
|
||||
inputUI += "<option>"+inputDesc.values[option]+"</option>";
|
||||
}
|
||||
inputUI += "</select>";
|
||||
}
|
||||
else {
|
||||
inputUI = "<input class=\"form-control\" type=\""+inputDesc.type+"\" name=\""+input+"\">";
|
||||
}
|
||||
$('#options').append(
|
||||
'<div class="row">\
|
||||
<div class="col-md-5 labels">\
|
||||
'+input+':\
|
||||
</div>\
|
||||
<div class="col-md-5">\
|
||||
'+inputUI+'\
|
||||
</div>\
|
||||
</div>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
// 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(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>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 489 KiB |
@@ -3,73 +3,92 @@
|
||||
<html>
|
||||
|
||||
|
||||
<head>
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.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="demo.js" charset="utf-8"></script>
|
||||
|
||||
</head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
<link rel="icon" sizes="192x192" href="../icons/ic_192.png">
|
||||
|
||||
|
||||
<body>
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<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 rel="stylesheet" href="demo.css">
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
|
||||
<script src="lib/urlHash.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>
|
||||
|
||||
<div class="container-fluid">
|
||||
</head>
|
||||
|
||||
<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 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>
|
||||
</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>
|
||||
</div>
|
||||
<body>
|
||||
|
||||
<section id="steps" class="row"></section>
|
||||
<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">
|
||||
|
||||
<section id="addStep">
|
||||
<div class="row">
|
||||
<select class="form-control">
|
||||
<option value="none" disabled selected>Select a new step...</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div id="options"></div>
|
||||
</div>
|
||||
<div class="row add">
|
||||
<button class="btn btn-success btn-lg" name="add">Add Step</button>
|
||||
</div>
|
||||
</section>
|
||||
<div class="container-fluid">
|
||||
|
||||
<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 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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
<section id="steps" class="row"></section>
|
||||
|
||||
<hr />
|
||||
|
||||
<section id="addStep" class="panel panel-primary">
|
||||
<div class="form-inline">
|
||||
<div class="panel-body">
|
||||
<div style="text-align:center;">
|
||||
<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>
|
||||
</div>
|
||||
<br />
|
||||
<p class="info" style="padding:8px;">Select a new module to add to your sequence.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var sequencer;
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
60
examples/lib/defaultHtmlSequencerUi.js
Normal file
60
examples/lib/defaultHtmlSequencerUi.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
function removeStepUi() {
|
||||
var index = $(removeStepSel).index(this) + 1;
|
||||
sequencer.removeSteps(index).run({index : sequencer.images.image1.steps.length-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
|
||||
*/
|
||||
_sequencer
|
||||
.addSteps(newStepName, options)
|
||||
.run({index: _sequencer.images.image1.steps.length - 2});
|
||||
|
||||
// add to URL hash too
|
||||
setUrlHashParameter("steps", _sequencer.toString());
|
||||
}
|
||||
|
||||
return {
|
||||
onLoad: onLoad,
|
||||
importStepsFromUrlHash: importStepsFromUrlHash,
|
||||
selectNewStepUi: selectNewStepUi,
|
||||
removeStepUi: removeStepUi,
|
||||
addStepUi: addStepUi
|
||||
}
|
||||
}
|
||||
185
examples/lib/defaultHtmlStepUi.js
Normal file
185
examples/lib/defaultHtmlStepUi.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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" 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>"
|
||||
);
|
||||
|
||||
function saveOptions() {
|
||||
$(step.ui.querySelector("div.details"))
|
||||
.find("input,select")
|
||||
.each(function(i, input) {
|
||||
step.options[$(input).attr("name")] = input.value;
|
||||
});
|
||||
_sequencer.run({index: _sequencer.images.image1.steps.length - 2});
|
||||
|
||||
// modify the url hash
|
||||
setUrlHashParameter("steps", _sequencer.toString());
|
||||
}
|
||||
|
||||
// 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');
|
||||
});
|
||||
|
||||
}
|
||||
BIN
icons/ic_144.png
Executable file
BIN
icons/ic_144.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
BIN
icons/ic_192.png
Executable file
BIN
icons/ic_192.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/ic_96.png
Executable file
BIN
icons/ic_96.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
173
index.js
173
index.js
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('./src/ImageSequencer');
|
||||
sequencer = ImageSequencer({ui: false});
|
||||
require("./src/ImageSequencer");
|
||||
sequencer = ImageSequencer({ ui: false });
|
||||
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);
|
||||
@@ -12,120 +13,140 @@ 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')
|
||||
.parse(process.argv);
|
||||
.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")
|
||||
.parse(process.argv);
|
||||
|
||||
// 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.
|
||||
if(!program.step || !validateSteps(program.step))
|
||||
exit("Please ensure all steps are valid.");
|
||||
if (!program.step || !validateSteps(program.step))
|
||||
exit("Please ensure all steps are valid.");
|
||||
|
||||
// If there's no user defined output directory, select a default directory.
|
||||
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]);
|
||||
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")
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
//Generate the Output Directory
|
||||
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")
|
||||
|
||||
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");
|
||||
|
||||
// Iterate through the steps and retrieve their inputs.
|
||||
program.step.forEach(function(step){
|
||||
program.step.forEach(function(step) {
|
||||
var options = Object.assign({}, sequencer.modulesInfo(step).inputs);
|
||||
|
||||
|
||||
// If inputs exists, print to console.
|
||||
if (Object.keys(options).length) {
|
||||
console.log("[" + step + "]: Inputs");
|
||||
}
|
||||
|
||||
|
||||
// If inputs exists, print them out with descriptions.
|
||||
Object.keys(options).forEach(function(input) {
|
||||
// The array below creates a variable number of spaces. This is done with (length + 1).
|
||||
// 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{
|
||||
|
||||
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`);
|
||||
} 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(program.config, options)) {
|
||||
console.log("Now using Options object");
|
||||
Object.keys(options).forEach(function (input) {
|
||||
Object.keys(options).forEach(function(input) {
|
||||
options[input] = program.config[input];
|
||||
})
|
||||
}
|
||||
else{
|
||||
});
|
||||
} 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;
|
||||
});
|
||||
}
|
||||
// Add the step and its inputs to the sequencer.
|
||||
sequencer.addSteps(step, options);
|
||||
});
|
||||
|
||||
|
||||
var spinnerObj = { succeed: () => {}, stop: () => {} };
|
||||
if (!process.env.TEST)
|
||||
spinnerObj = Spinner("Your Image is being processed..").start();
|
||||
|
||||
// Run the sequencer.
|
||||
sequencer.run(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);
|
||||
|
||||
//check if spinner was not overriden stop it
|
||||
if (!spinnerObj.overrideFlag) {
|
||||
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.
|
||||
// Assume all are valid in the beginning.
|
||||
var valid = true;
|
||||
steps.forEach(function(step) {
|
||||
// If any step in the array is not valid (not a property of modulesInfo), set valid to false.
|
||||
@@ -133,25 +154,29 @@ function validateSteps(steps) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Return valid. (If all of the steps are valid properties, valid will have remained true).
|
||||
return valid;
|
||||
}
|
||||
|
||||
//Takes config and options object and checks if all the keys exist in config
|
||||
function validateConfig(config_,options_){
|
||||
//Takes config and options object and checks if all the keys exist in config
|
||||
function validateConfig(config_, options_) {
|
||||
options_ = Object.keys(options_);
|
||||
if (
|
||||
(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`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})()
|
||||
== false)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
(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`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})() == false
|
||||
)
|
||||
return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "image-sequencer",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"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/replace.js test/modules/import-export.js test/modules/run.js | tape-run --render=\"tap-spec\"",
|
||||
"start": "grunt serve"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,10 +29,12 @@
|
||||
"fisheyegl": "^0.1.2",
|
||||
"font-awesome": "~4.5.0",
|
||||
"get-pixels": "~3.3.0",
|
||||
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2",
|
||||
"jquery": "~2",
|
||||
"jsqr": "^0.2.2",
|
||||
"lodash": "^4.17.5",
|
||||
"ndarray-gaussian-filter": "^1.0.0",
|
||||
"ora": "^2.0.0",
|
||||
"pace": "0.0.4",
|
||||
"readline-sync": "^1.4.7",
|
||||
"save-pixels": "~2.3.4",
|
||||
@@ -41,9 +44,10 @@
|
||||
"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,29 +1,37 @@
|
||||
function AddStep(ref, image, name, o) {
|
||||
// add steps to the sequencer
|
||||
// TODO: reduce redundancy with InsertStep; this should be a specific usage of InsertStep at the final position
|
||||
function AddStep(_sequencer, image, name, o) {
|
||||
|
||||
function addStep(image, name, o_) {
|
||||
var o = ref.copy(o_);
|
||||
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o_.name || name;
|
||||
if (_sequencer.modules[name]) var moduleInfo = _sequencer.modules[name][1];
|
||||
else console.log('Module ' + name + ' not found.');
|
||||
|
||||
var o = _sequencer.copy(o_);
|
||||
o.number = _sequencer.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.container = o_.container || _sequencer.options.selector;
|
||||
o.image = image;
|
||||
o.inBrowser = ref.options.inBrowser;
|
||||
o.inBrowser = _sequencer.options.inBrowser;
|
||||
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui,
|
||||
inBrowser: _sequencer.options.inBrowser,
|
||||
ui: _sequencer.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](o,UI);
|
||||
ref.images[image].steps.push(module);
|
||||
var UI = _sequencer.events;
|
||||
var module = _sequencer.modules[name][0](o, UI);
|
||||
_sequencer.images[image].steps.push(module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addStep(image, name, o);
|
||||
_sequencer.steps = _sequencer.images[image].steps;
|
||||
}
|
||||
module.exports = AddStep;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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;
|
||||
// if (options.inBrowser) options.ui = options.ui || require('./UserInterface');
|
||||
options.sequencerCounter = 0;
|
||||
|
||||
function objTypeOf(object){
|
||||
@@ -37,13 +38,13 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
}
|
||||
|
||||
var image,
|
||||
steps = [],
|
||||
modules = require('./Modules'),
|
||||
formatInput = require('./FormatInput'),
|
||||
images = {},
|
||||
inputlog = [],
|
||||
events = require('./UserInterface')(),
|
||||
fs = require('fs');
|
||||
steps = [],
|
||||
modules = require('./Modules'),
|
||||
formatInput = require('./FormatInput'),
|
||||
images = {},
|
||||
inputlog = [],
|
||||
events = require('./ui/UserInterface')(),
|
||||
fs = require('fs');
|
||||
|
||||
// if in browser, prompt for an image
|
||||
// if (options.imageSelect || options.inBrowser) addStep('image-select');
|
||||
@@ -59,8 +60,8 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
inputlog.push({method:"addSteps", json_q:copy(json_q)});
|
||||
|
||||
for (var i in json_q)
|
||||
for (var j in json_q[i])
|
||||
require("./AddStep")(this_,i,json_q[i][j].name,json_q[i][j].o);
|
||||
for (var j in json_q[i])
|
||||
require("./AddStep")(this_,i,json_q[i][j].name,json_q[i][j].o);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
indices = json_q[img].sort(function(a,b){return b-a});
|
||||
run[img] = indices[indices.length-1];
|
||||
for (var i in indices)
|
||||
removeStep(img,indices[i]);
|
||||
removeStep(img,indices[i]);
|
||||
}
|
||||
// this.run(run); // This is creating problems
|
||||
return this;
|
||||
@@ -107,26 +108,42 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
var details = json_q[img];
|
||||
details = details.sort(function(a,b){return b.index-a.index});
|
||||
for (var i in details)
|
||||
require("./InsertStep")(this_,img,details[i].index,details[i].name,details[i].o);
|
||||
require("./InsertStep")(this_,img,details[i].index,details[i].name,details[i].o);
|
||||
run[img] = details[details.length-1].index;
|
||||
}
|
||||
// this.run(run); // This is Creating issues
|
||||
return this;
|
||||
}
|
||||
|
||||
function run(t_image,t_from) {
|
||||
// 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;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
for (var arg in arguments) args.push(copy(arguments[arg]));
|
||||
|
||||
var callback = function() {};
|
||||
for (var arg in args)
|
||||
if(objTypeOf(args[arg]) == "Function")
|
||||
callback = args.splice(arg,1)[0];
|
||||
if(objTypeOf(args[arg]) == "Function")
|
||||
callback = args.splice(arg,1)[0];
|
||||
|
||||
var json_q = formatInput.call(this_,args,"r");
|
||||
|
||||
require('./Run')(this_, json_q, callback);
|
||||
require('./Run')(this_, json_q, callback,index,progressObj);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -139,7 +156,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
inputlog.push({method:"loadImages", json_q:copy(json_q)});
|
||||
var loadedimages = this.copy(json_q.loadedimages);
|
||||
// require('./LoadImage')(this,i,json_q.images[i]);
|
||||
|
||||
var ret = {
|
||||
name: "ImageSequencer Wrapper",
|
||||
@@ -159,7 +175,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
return;
|
||||
}
|
||||
var img = loadedimages[i];
|
||||
require('./LoadImage')(sequencer,img,json_q.images[img],function(){
|
||||
require('./ui/LoadImage')(sequencer,img,json_q.images[img],function(){
|
||||
load(++i);
|
||||
});
|
||||
}
|
||||
@@ -174,7 +190,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
}
|
||||
|
||||
function setUI(UI) {
|
||||
this.events = require('./UserInterface')(UI);
|
||||
this.events = require('./ui/UserInterface')(UI);
|
||||
}
|
||||
|
||||
var exportBin = function(dir,basic) {
|
||||
@@ -185,13 +201,97 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
var modulesdata = {}
|
||||
if(name == "load-image") return {};
|
||||
if(arguments.length==0)
|
||||
for (var modulename in modules) {
|
||||
modulesdata[modulename] = modules[modulename][1];
|
||||
}
|
||||
for (var modulename in modules) {
|
||||
modulesdata[modulename] = modules[modulename][1];
|
||||
}
|
||||
else modulesdata = modules[name][1];
|
||||
return modulesdata;
|
||||
}
|
||||
|
||||
// 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(str){
|
||||
return this.stringToJSON(this.toString());
|
||||
}
|
||||
|
||||
// Coverts stringified sequence into an array of JSON steps
|
||||
function stringToJSON(str){
|
||||
let steps = str.split(',');
|
||||
return steps.map(stringToJSONstep);
|
||||
}
|
||||
|
||||
// Converts one stringified step into JSON
|
||||
function stringToJSONstep(str){
|
||||
if(str.indexOf('(') === -1) { // if there are no settings specified
|
||||
var moduleName = str.substr(0);
|
||||
stepSettings = "";
|
||||
} else {
|
||||
var moduleName = str.substr(0, str.indexOf('('));
|
||||
stepSettings = str.slice(str.indexOf('(') + 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 = 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);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
//literals and objects
|
||||
name: "ImageSequencer",
|
||||
@@ -212,11 +312,20 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
setUI: setUI,
|
||||
exportBin: exportBin,
|
||||
modulesInfo: modulesInfo,
|
||||
toString: toString,
|
||||
stepToString: stepToString,
|
||||
toJSON: toJSON,
|
||||
stringToJSON: stringToJSON,
|
||||
stringToJSONstep: stringToJSONstep,
|
||||
importString: importString,
|
||||
importJSON: importJSON,
|
||||
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy
|
||||
copy: copy,
|
||||
|
||||
setInputStep: require('./ui/SetInputStep')(sequencer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
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) {
|
||||
|
||||
function insertStep(image, index, name, o_) {
|
||||
@@ -8,7 +11,7 @@ function InsertStep(ref, image, index, name, o) {
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.image = image;
|
||||
|
||||
if(index==-1) index = ref.images[image].steps.length;
|
||||
if (index == -1) index = ref.images[image].steps.length;
|
||||
|
||||
o.step = {
|
||||
name: o.name,
|
||||
@@ -21,8 +24,8 @@ 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);
|
||||
var module = ref.modules[name][0](o, UI);
|
||||
ref.images[image].steps.splice(index, 0, module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,40 +2,49 @@
|
||||
* Core modules and their info files
|
||||
*/
|
||||
module.exports = {
|
||||
'green-channel': [
|
||||
require('./modules/GreenChannel/Module'),require('./modules/GreenChannel/info')
|
||||
'channel': [
|
||||
require('./modules/Channel/Module'), require('./modules/Channel/info')
|
||||
],
|
||||
'brightness': [
|
||||
require('./modules/Brightness/Module'),require('./modules/Brightness/info')
|
||||
require('./modules/Brightness/Module'), require('./modules/Brightness/info')
|
||||
],
|
||||
'edge-detect':[
|
||||
require('./modules/EdgeDetect/Module'),require('./modules/EdgeDetect/info')
|
||||
'edge-detect': [
|
||||
require('./modules/EdgeDetect/Module'), require('./modules/EdgeDetect/info')
|
||||
],
|
||||
'ndvi-red': [
|
||||
require('./modules/NdviRed/Module'),require('./modules/NdviRed/info')
|
||||
'ndvi': [
|
||||
require('./modules/Ndvi/Module'), require('./modules/Ndvi/info')
|
||||
],
|
||||
'invert': [
|
||||
require('./modules/Invert/Module'),require('./modules/Invert/info')
|
||||
require('./modules/Invert/Module'), require('./modules/Invert/info')
|
||||
],
|
||||
'crop': [
|
||||
require('./modules/Crop/Module'),require('./modules/Crop/info')
|
||||
require('./modules/Crop/Module'), require('./modules/Crop/info')
|
||||
],
|
||||
'segmented-colormap': [
|
||||
require('./modules/SegmentedColormap/Module'),require('./modules/SegmentedColormap/info')
|
||||
'colormap': [
|
||||
require('./modules/Colormap/Module'), require('./modules/Colormap/info')
|
||||
],
|
||||
'decode-qr': [
|
||||
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
|
||||
require('./modules/DecodeQr/Module'), require('./modules/DecodeQr/info')
|
||||
],
|
||||
'fisheye-gl': [
|
||||
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
|
||||
require('./modules/FisheyeGl/Module'), require('./modules/FisheyeGl/info')
|
||||
],
|
||||
'dynamic': [
|
||||
require('./modules/Dynamic/Module'),require('./modules/Dynamic/info')
|
||||
require('./modules/Dynamic/Module'), require('./modules/Dynamic/info')
|
||||
],
|
||||
'blur': [
|
||||
require('./modules/Blur/Module'),require('./modules/Blur/info')
|
||||
require('./modules/Blur/Module'), require('./modules/Blur/info')
|
||||
],
|
||||
'saturation': [
|
||||
require('./modules/Saturation/Module'),require('./modules/Saturation/info')
|
||||
require('./modules/Saturation/Module'), require('./modules/Saturation/info')
|
||||
],
|
||||
'average': [
|
||||
require('./modules/Average/Module'), require('./modules/Average/info')
|
||||
],
|
||||
'blend': [
|
||||
require('./modules/Blend/Module'), require('./modules/Blend/info')
|
||||
],
|
||||
'import-image': [
|
||||
require('./modules/ImportImage/Module'), require('./modules/ImportImage/info')
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// Uses a given image as input and replaces it with the output.
|
||||
// Works only in the browser.
|
||||
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]);
|
||||
@@ -10,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);
|
||||
@@ -28,7 +42,7 @@ function ReplaceImage(ref,selector,steps,options) {
|
||||
|
||||
function make(url) {
|
||||
tempSequencer.loadImage(url, function(){
|
||||
this.addSteps(steps).run(function(out){
|
||||
this.addSteps(steps).run({stop:function(){}},function(out){
|
||||
img.src = out;
|
||||
});
|
||||
});
|
||||
|
||||
59
src/Run.js
59
src/Run.js
@@ -1,55 +1,80 @@
|
||||
function Run(ref, json_q, callback) {
|
||||
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') {
|
||||
var image = drawarray[pos - 1].image;
|
||||
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;
|
||||
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() {
|
||||
drawStep(drawarray, ++pos);
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
ref.images[image].steps[i].draw(
|
||||
ref.copy(input),
|
||||
function onEachStep() {
|
||||
drawStep(drawarray, ++pos);
|
||||
},
|
||||
progressObj
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawSteps(json_q) {
|
||||
var drawarray = [];
|
||||
for (var image in json_q) {
|
||||
var no_steps = ref.images[image].steps.length;
|
||||
var init = json_q[image];
|
||||
for(var i = 0; i < no_steps - init; i++) {
|
||||
for (var i = 0; i < no_steps - init; i++) {
|
||||
drawarray.push({ image: image, i: init + i });
|
||||
}
|
||||
}
|
||||
drawStep(drawarray,0);
|
||||
drawStep(drawarray, ind);
|
||||
}
|
||||
|
||||
function filter(json_q){
|
||||
|
||||
function filter(json_q) {
|
||||
for (var image in json_q) {
|
||||
if (json_q[image] == 0 && ref.images[image].steps.length == 1)
|
||||
delete json_q[image];
|
||||
delete json_q[image];
|
||||
else if (json_q[image] == 0) json_q[image]++;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
var json_q = filter(json_q);
|
||||
return drawSteps(json_q);
|
||||
|
||||
}
|
||||
module.exports = Run;
|
||||
|
||||
88
src/modules/Average/Module.js
Executable file
88
src/modules/Average/Module.js
Executable file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Average all pixel colors
|
||||
*/
|
||||
module.exports = function Average(options, UI){
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
options.step.metadata = options.step.metadata || {};
|
||||
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
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){
|
||||
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
|
||||
};
|
||||
|
||||
// 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, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
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": {
|
||||
}
|
||||
}
|
||||
74
src/modules/Blend/Module.js
Normal file
74
src/modules/Blend/Module.js
Normal file
@@ -0,0 +1,74 @@
|
||||
module.exports = function Dynamic(options, UI, util) {
|
||||
|
||||
options = options || {};
|
||||
options.func = options.func || "function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }";
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
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 };
|
||||
|
||||
// This output is accessible by the UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell the UI that the draw is complete
|
||||
UI.onComplete(options.step);
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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 ] }"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@
|
||||
*/
|
||||
module.exports = function Blur(options,UI){
|
||||
options = options || {};
|
||||
options.title = "Blur";
|
||||
options.description = "Blur an Image";
|
||||
options.blur = options.blur || 2
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback){
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blur",
|
||||
"description": "Blur an image by a given value",
|
||||
"name": "Blur",
|
||||
"description": "Gaussian blur an image by a given value, typically 0-5",
|
||||
"inputs": {
|
||||
"blur": {
|
||||
"type": "integer",
|
||||
|
||||
@@ -4,15 +4,22 @@
|
||||
|
||||
module.exports = function Brightness(options,UI){
|
||||
options = options || {};
|
||||
options.title = "Brightness";
|
||||
options.description = "Changes the brightness of the image";
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback){
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
/*
|
||||
In this case progress is handled by changepixel internally otherwise progressObj
|
||||
needs to be overriden and used
|
||||
For eg. progressObj = new SomeProgressModule()
|
||||
*/
|
||||
|
||||
// Tell the UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
@@ -55,4 +62,4 @@ module.exports = function Brightness(options,UI){
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Brightness",
|
||||
"description": "Change the brightness of the image by given value",
|
||||
"description": "Change the brightness of the image by given percent value",
|
||||
"inputs": {
|
||||
"brightness": {
|
||||
"type": "integer",
|
||||
@@ -8,4 +8,4 @@
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
/*
|
||||
* Display only the green channel
|
||||
* Display only one color channel
|
||||
*/
|
||||
module.exports = function GreenChannel(options,UI) {
|
||||
module.exports = function Channel(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Green channel only";
|
||||
options.description = "Displays only the green channel of an image";
|
||||
options.channel = options.channel || "green";
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
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) {
|
||||
return [0, g, 0, a];
|
||||
if (options.channel == "red") return [r, 0, 0, a];
|
||||
if (options.channel == "green") return [0, g, 0, a];
|
||||
if (options.channel == "blue") return [0, 0, b, a];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
12
src/modules/Channel/info.json
Normal file
12
src/modules/Channel/info.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Channel",
|
||||
"description": "Displays only one color channel of an image -- default is green",
|
||||
"inputs": {
|
||||
"channel": {
|
||||
"type": "select",
|
||||
"desc": "Color channel",
|
||||
"default": "green",
|
||||
"values": ["red", "green", "blue"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@
|
||||
* Adapted from bgamari's work in Infragram: https://github.com/p-v-o-s/infragram-js/commit/346c97576a07b71a55671d17e0153b7df74e803b
|
||||
*/
|
||||
|
||||
module.exports = function SegmentedColormap(value, options) {
|
||||
module.exports = function Colormap(value, options) {
|
||||
options.colormap = options.colormap || colormaps.default;
|
||||
// if a lookup table is provided as an array:
|
||||
if(typeof(options.colormap) == "object")
|
||||
colormapFunction = segmented_colormap(options.colormap);
|
||||
colormapFunction = colormap(options.colormap);
|
||||
// if a stored colormap is named with a string like "fastie":
|
||||
else if(colormaps.hasOwnProperty(options.colormap))
|
||||
colormapFunction = colormaps[options.colormap];
|
||||
@@ -23,7 +23,7 @@ module.exports = function SegmentedColormap(value, options) {
|
||||
return colormapFunction(value / 255.00);
|
||||
}
|
||||
|
||||
function segmented_colormap(segments) {
|
||||
function colormap(segments) {
|
||||
return function(x) {
|
||||
var i, result, x0, x1, xstart, y0, y1, _i, _j, _len, _ref, _ref1, _ref2, _ref3;
|
||||
_ref = [0, 0], y0 = _ref[0], y1 = _ref[1];
|
||||
@@ -52,29 +52,29 @@ function segmented_colormap(segments) {
|
||||
};
|
||||
|
||||
var colormaps = {
|
||||
greyscale: segmented_colormap([
|
||||
greyscale: colormap([
|
||||
[0, [0, 0, 0], [255, 255, 255] ],
|
||||
[1, [255, 255, 255], [255, 255, 255] ]
|
||||
]),
|
||||
default: segmented_colormap([
|
||||
default: colormap([
|
||||
[0, [0, 0, 255], [0, 255, 0] ],
|
||||
[0.25, [0, 255, 0], [255, 255, 0] ],
|
||||
[0.50, [0, 255, 255], [255, 255, 0] ],
|
||||
[0.75, [255, 255, 0], [255, 0, 0] ]
|
||||
]),
|
||||
ndvi: segmented_colormap([
|
||||
ndvi: colormap([
|
||||
[0, [0, 0, 255], [38, 195, 195] ],
|
||||
[0.5, [0, 150, 0], [255, 255, 0] ],
|
||||
[0.75, [255, 255, 0], [255, 50, 50] ]
|
||||
]),
|
||||
stretched: segmented_colormap([
|
||||
stretched: colormap([
|
||||
[0, [0, 0, 255], [0, 0, 255] ],
|
||||
[0.1, [0, 0, 255], [38, 195, 195] ],
|
||||
[0.5, [0, 150, 0], [255, 255, 0] ],
|
||||
[0.7, [255, 255, 0], [255, 50, 50] ],
|
||||
[0.9, [255, 50, 50], [255, 50, 50] ]
|
||||
]),
|
||||
fastie: segmented_colormap([
|
||||
fastie: colormap([
|
||||
[0, [255, 255, 255], [0, 0, 0] ],
|
||||
[0.167, [0, 0, 0], [255, 255, 255] ],
|
||||
[0.33, [255, 255, 255], [0, 0, 0] ],
|
||||
@@ -1,14 +1,16 @@
|
||||
module.exports = function SegmentedColormap(options,UI) {
|
||||
module.exports = function Colormap(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Segmented Colormap";
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input,callback) {
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
@@ -16,7 +18,7 @@ module.exports = function SegmentedColormap(options,UI) {
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
var combined = (r + g + b) / 3.000;
|
||||
var res = require('./SegmentedColormap')(combined, options);
|
||||
var res = require('./Colormap')(combined, options);
|
||||
return [res[0], res[1], res[2], 255];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Segmented Colormap",
|
||||
"description": "Maps brightness values (average of red, green & blue) to a given color lookup table, made up of a set of one more color gradients.",
|
||||
"name": "Colormap",
|
||||
"description": "Maps brightness values (average of red, green & blue) to a given color lookup table, made up of a set of one more color gradients.\n\nFor example, 'cooler' colors like blue could represent low values, while 'hot' colors like red could represent high values.",
|
||||
"inputs": {
|
||||
"colormap": {
|
||||
"type": "select",
|
||||
@@ -13,47 +13,65 @@
|
||||
* y = options.y
|
||||
* y = options.y + options.h
|
||||
*/
|
||||
module.exports = function CropModule(options,UI) {
|
||||
module.exports = function CropModule(options, UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Crop Image";
|
||||
// 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;
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step); // we should get UI to return the image thumbnail so we can attach our own UI extensions
|
||||
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
// add our custom in-module html ui:
|
||||
if (options.step.inBrowser) var ui = require('./Ui.js')(options.step, UI);
|
||||
var output,
|
||||
setupComplete = false;
|
||||
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
require('./Crop')(input,options,function(out,format){
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: out,
|
||||
format: format
|
||||
}
|
||||
// save the input image;
|
||||
// TODO: this should be moved to module API to persist the input image
|
||||
options.step.input = input.src;
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = out;
|
||||
require('./Crop')(input, options, function(out, format){
|
||||
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: out,
|
||||
format: format
|
||||
}
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
// This output is accessible to the UI
|
||||
options.step.output = out;
|
||||
|
||||
});
|
||||
// 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();
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
// 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();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Crop",
|
||||
"description": "Crop image to given x, y, w, h",
|
||||
"description": "Crop image to given x, y, w, h in pixels, measured from top left",
|
||||
"url": "https://github.com/publiclab/image-sequencer/tree/master/MODULES.md",
|
||||
"inputs": {
|
||||
"x": {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Decode QR Code";
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "Decode QR",
|
||||
"description": "Search for and decode a QR code in the image",
|
||||
"inputs": {
|
||||
},
|
||||
"outputs": {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
module.exports = function Dynamic(options,UI) {
|
||||
|
||||
|
||||
options = options || {};
|
||||
options.title = "Dynamic";
|
||||
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input,callback) {
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
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
|
||||
options.monochrome = options.monochrome || "(R+G+B)/3";
|
||||
|
||||
|
||||
function generator(expression) {
|
||||
var func = 'f = function (r, g, b, a) { var R = r, G = g, B = b, A = a;'
|
||||
func = func + 'return ';
|
||||
@@ -25,48 +27,65 @@ module.exports = function Dynamic(options,UI) {
|
||||
eval(func);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
var channels = ['red', 'green', 'blue', 'alpha'];
|
||||
|
||||
|
||||
channels.forEach(function(channel) {
|
||||
if (options.hasOwnProperty(channel)) options[channel + '_function'] = generator(options[channel]);
|
||||
else if (channel === 'alpha') options['alpha_function'] = function() { return 255; }
|
||||
else options[channel + '_function'] = generator(options.monochrome);
|
||||
});
|
||||
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
|
||||
/* neighbourpixels can be calculated by
|
||||
this.getNeighbourPixel.fun(x,y) or this.getNeighborPixel.fun(x,y)
|
||||
*/
|
||||
var combined = (r + g + b) / 3.000;
|
||||
return [
|
||||
options.red_function( r, g, b, a),
|
||||
options.red_function(r, g, b, a),
|
||||
options.green_function(r, g, b, a),
|
||||
options.blue_function( r, g, b, a),
|
||||
options.blue_function(r, g, b, a),
|
||||
options.alpha_function(r, g, b, a),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/* Functions to get the neighbouring pixel by position (x,y) */
|
||||
function getNeighbourPixel(pixels,curX,curY,distX,distY){
|
||||
return [
|
||||
pixels.get(curX+distX,curY+distY,0)
|
||||
,pixels.get(curX+distX,curY+distY,1)
|
||||
,pixels.get(curX+distX,curY+distY,2)
|
||||
,pixels.get(curX+distX,curY+distY,3)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
|
||||
|
||||
// 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,
|
||||
changePixel: changePixel,
|
||||
getNeighbourPixel: getNeighbourPixel,
|
||||
getNeighborPixel: getNeighbourPixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const _ = require('lodash')
|
||||
var pace = require('pace')
|
||||
|
||||
//define kernels for the sobel filter
|
||||
const kernelx = [[-1,0,1],[-2,0,2],[-1,0,1]],
|
||||
@@ -12,7 +11,6 @@ let weakEdgePixels = []
|
||||
let notInUI
|
||||
module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio,inBrowser){
|
||||
notInUI = !inBrowser
|
||||
if(notInUI) var progressbar1 = pace((pixels.shape[0] * pixels.shape[1]))
|
||||
for(var x = 0; x < pixels.shape[0]; x++) {
|
||||
angles.push([])
|
||||
mags.push([])
|
||||
@@ -33,7 +31,6 @@ module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio
|
||||
|
||||
mags.slice(-1)[0].push(pixel[3])
|
||||
angles.slice(-1)[0].push(result.angle)
|
||||
if(notInUI)progressbar1.op()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +72,6 @@ function changePixel(pixels,val,a,x,y){
|
||||
function nonMaxSupress(pixels) {
|
||||
angles = angles.map((arr)=>arr.map(convertToDegrees))
|
||||
|
||||
if(notInUI) var progressbar2 = pace((pixels.shape[0] * pixels.shape[1]))
|
||||
for(let i = 1;i<pixels.shape[0]-1;i++){
|
||||
for(let j=1;j<pixels.shape[1]-1;j++){
|
||||
|
||||
@@ -118,7 +114,6 @@ function nonMaxSupress(pixels) {
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
|
||||
if(notInUI) progressbar2.op()
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
@@ -133,7 +128,6 @@ var findMaxInMatrix = arr => Math.max(...arr.map(el=>el.map(val=>!!val?val:0)).m
|
||||
function doubleThreshold(pixels,highThresholdRatio,lowThresholdRatio){
|
||||
const highThreshold = findMaxInMatrix(mags) * 0.2
|
||||
const lowThreshold = highThreshold * lowThresholdRatio
|
||||
if(notInUI) var progressbar3 = pace((pixels.shape[0] * pixels.shape[1]))
|
||||
|
||||
for(let i =0;i<pixels.shape[0];i++){
|
||||
for(let j=0;j<pixels.shape[1];j++){
|
||||
@@ -144,7 +138,6 @@ function doubleThreshold(pixels,highThresholdRatio,lowThresholdRatio){
|
||||
?strongEdgePixels.push(pixelPos)
|
||||
:weakEdgePixels.push(pixelPos)
|
||||
:pixels.set(i,j,3,0)
|
||||
if(notInUI) progressbar3.op()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
module.exports = function edgeDetect(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Detect Edges";
|
||||
options.description = "Detects the edges in an image";
|
||||
options.blur = options.blur || 2
|
||||
options.highThresholdRatio = options.highThresholdRatio||0.2
|
||||
options.lowThresholdRatio = options.lowThresholdRatio||0.15
|
||||
@@ -15,8 +13,11 @@ module.exports = function edgeDetect(options,UI) {
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input,callback) {
|
||||
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
|
||||
@@ -63,4 +64,4 @@ module.exports = function edgeDetect(options,UI) {
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
"default": 0.15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Fisheye GL";
|
||||
|
||||
var output;
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"name": "Fisheye GL",
|
||||
"description": "Correct fisheye, or barrel distortion, in images (with WebGL -- adapted from fisheye-correction-webgl by @bluemir).",
|
||||
"requires": [ "webgl" ],
|
||||
"inputs": {
|
||||
"a": {
|
||||
"type": "float",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Green Channel",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
68
src/modules/ImportImage/Module.js
Normal file
68
src/modules/ImportImage/Module.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 = options || {};
|
||||
options.imageUrl = options.url || "./images/monarch.png";
|
||||
|
||||
var output,
|
||||
imgObj = new Image();
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step); // 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) {
|
||||
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
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
|
||||
}
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = imgObj.src;
|
||||
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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,32 +1,32 @@
|
||||
/*
|
||||
* Invert the image
|
||||
*/
|
||||
module.exports = function Invert(options,UI) {
|
||||
module.exports = function Invert(options, UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Invert Colors";
|
||||
options.description = "Inverts the colors of the image";
|
||||
|
||||
// Tell UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input,callback) {
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
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) {
|
||||
return [255-r, 255-g, 255-b, a];
|
||||
return [255 - r, 255 - g, 255 - b, a];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
@@ -48,7 +48,7 @@ module.exports = function Invert(options,UI) {
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
/*
|
||||
* NDVI with red filter (blue channel is infrared)
|
||||
*/
|
||||
module.exports = function NdviRed(options,UI) {
|
||||
module.exports = function Ndvi(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "NDVI for red-filtered cameras (blue is infrared)";
|
||||
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.
|
||||
function draw(input,callback) {
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
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) {
|
||||
var ndvi = (b - r) / (1.00 * b + r);
|
||||
if (options.filter == "red") var ndvi = (b - r) / (1.00 * b + r);
|
||||
if (options.filter == "blue") var ndvi = (r - b) / (1.00 * b + r);
|
||||
var x = 255 * (ndvi + 1) / 2;
|
||||
return [x, x, x, a];
|
||||
}
|
||||
12
src/modules/Ndvi/info.json
Normal file
12
src/modules/Ndvi/info.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "NDVI",
|
||||
"description": "Normalized Difference Vegetation Index, or NDVI, is an image analysis technique used with aerial photography. It's a way to visualize the amounts of infrared and other wavelengths of light reflected from vegetation by comparing ratios of blue and red light absorbed versus green and IR light reflected. NDVI is used to evaluate the health of vegetation in satellite imagery, where it correlates with how much photosynthesis is happening. This is helpful in assessing vegetative health or stress. <a href='https://publiclab.org/ndvi'>Read more</a>.<br /><br/>This is designed for use with red-filtered single camera <a href='http://publiclab.org/infragram'>DIY Infragram cameras</a>; change to 'blue' for blue filters",
|
||||
"inputs": {
|
||||
"filter": {
|
||||
"type": "select",
|
||||
"desc": "Filter color",
|
||||
"default": "red",
|
||||
"values": ["red", "blue"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "NDVI Red",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
/*
|
||||
* Saturate an image
|
||||
* Saturate an image with a value from 0 to 1
|
||||
*/
|
||||
module.exports = function Saturation(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Saturation";
|
||||
options.description = "Saturate an image";
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "Saturation",
|
||||
"description": "Change the saturation of the image by given value",
|
||||
"description": "Change the saturation of the image by given value, from 0-1, with 1 being 100% saturated.",
|
||||
"inputs": {
|
||||
"saturation": {
|
||||
"type": "integer",
|
||||
"desc": "saturation for the new image between 0 and 2, 0 being black and white and 2 being fully saturated",
|
||||
"desc": "saturation for the new image between 0 and 2, 0 being black and white and 2 being highly saturated",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
module.exports = function ImageThreshold(options) {
|
||||
options = options || {};
|
||||
options.title = "Threshold image";
|
||||
options.threshold = options.threshold || 30;
|
||||
|
||||
var image;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Threshold image",
|
||||
"description": "...",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,70 +3,86 @@
|
||||
* accepting a changePixel() method to remix a pixel's channels
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
var getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
getPixels(image.src, function(err, pixels) {
|
||||
|
||||
if(err) {
|
||||
console.log('Bad image path');
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (options.getNeighbourPixel) {
|
||||
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)
|
||||
var pace = require('pace')((pixels.shape[0] * pixels.shape[1]))
|
||||
|
||||
for(var x = 0; x < pixels.shape[0]; x++) {
|
||||
for(var y = 0; y < pixels.shape[1]; y++) {
|
||||
|
||||
|
||||
if (!options.inBrowser && !process.env.TEST) {
|
||||
try {
|
||||
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]);
|
||||
pixels.set(x, y, 1, pixel[1]);
|
||||
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
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format, {quality: 100});
|
||||
|
||||
r.on('data', function(chunk){
|
||||
var r = savePixels(pixels, options.format, { quality: 100 });
|
||||
|
||||
r.on("data", function (chunk) {
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
r.on('end', function(){
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
if (options.output) options.output(options.image,datauri,options.format);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ function LoadImage(ref, name, src, main_callback) {
|
||||
function loadImage(name, src) {
|
||||
var step = {
|
||||
name: "load-image",
|
||||
description: "This initial step loads and displays the original image without any modifications.",
|
||||
description: "This initial step loads and displays the original image without any modifications.<br /><br />To work with a new or different image, drag one into the drop zone.",
|
||||
ID: ref.options.sequencerCounter++,
|
||||
imageName: name,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
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;
|
||||
@@ -5,14 +5,12 @@
|
||||
module.exports = function UserInterface(events = {}) {
|
||||
|
||||
events.onSetup = events.onSetup || function(step) {
|
||||
if(step.ui == false) {
|
||||
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,27 +31,23 @@ 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+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
events.onRemove = events.onRemove || function(step) {
|
||||
if(step.ui == false){
|
||||
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+"\".");
|
||||
}
|
||||
29
src/util/GetFormat.js
Normal file
29
src/util/GetFormat.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
return format;
|
||||
|
||||
}
|
||||
18
src/util/getStep.js
Normal file
18
src/util/getStep.js
Normal file
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
},
|
||||
|
||||
getOuput : function(offset){
|
||||
return this.getStep(offset).output;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,10 +41,10 @@ test('addSteps is two-way chainable.', function (t){
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.addSteps("image3","ndvi-red");
|
||||
var returnval = sequencer.addSteps("image3","ndvi");
|
||||
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
|
||||
t.equal(sequencer.images.image3.steps.length,3,"Step length increased");
|
||||
t.equal(sequencer.images.image3.steps[2].options.name,"ndvi-red","Correct Step Added");
|
||||
t.equal(sequencer.images.image3.steps[2].options.name,"ndvi","Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -79,9 +79,9 @@ test('insertSteps is two-way chainable.', function (t){
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.insertSteps("image5",1,"ndvi-red");
|
||||
var returnval = sequencer.insertSteps("image5",1,"ndvi");
|
||||
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
|
||||
t.equal(sequencer.images.image5.steps.length,3);
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"ndvi-red","Correct Step Inserrted");
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"ndvi","Correct Step Inserrted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ sequencer.loadImages(test_png);
|
||||
sequencer.addSteps(['invert','invert']);
|
||||
|
||||
test("Preload", function(t) {
|
||||
sequencer.run(function(){
|
||||
sequencer.run({mode:'test'},function(){
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@@ -51,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(function(){
|
||||
this.addSteps('decode-qr').run({mode:'test'},function(){
|
||||
t.end();
|
||||
});
|
||||
})
|
||||
@@ -64,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(function(out){
|
||||
this.addSteps('invert').run({mode:'test'},function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
@@ -73,9 +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(function(out){
|
||||
this.addSteps('invert').run({mode:'test'},function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,21 +9,21 @@ var test = require('tape');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
// This function is used to test whether or not any additional global variables are being created
|
||||
function copy(g,a) {
|
||||
function copy(g, a) {
|
||||
var b = {};
|
||||
var i = 0;
|
||||
for (var v in a)
|
||||
if(g) {
|
||||
if(v != "sequencer") b[v] = a[v];
|
||||
}
|
||||
else {
|
||||
if(v != "sequencer" && v!="global1" && v!="global2" && !global1.hasOwnProperty(v)) i++;
|
||||
}
|
||||
if(g) return b;
|
||||
if (g) {
|
||||
if (v != "sequencer") b[v] = a[v];
|
||||
}
|
||||
else {
|
||||
if (v != "sequencer" && v != "global1" && v != "global2" && !global1.hasOwnProperty(v)) i++;
|
||||
}
|
||||
if (g) return b;
|
||||
else return i;
|
||||
}
|
||||
var parent = (typeof(global)==="undefined")?window:global;
|
||||
var global1 = copy(true,parent);
|
||||
var parent = (typeof (global) === "undefined") ? window : global;
|
||||
var global1 = copy(true, parent);
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
@@ -33,14 +33,14 @@ test('Image Sequencer has tests', function (t) {
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages loads a DataURL image and creates a step.', function (t){
|
||||
sequencer.loadImages('test',red);
|
||||
test('loadImages loads a DataURL image and creates a step.', function (t) {
|
||||
sequencer.loadImages('test', red);
|
||||
t.equal(sequencer.images.test.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.test.steps[0].output.src), "string", "Initial output exists");
|
||||
t.equal(typeof (sequencer.images.test.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('modulesInfo() returns info for each module', function (t){
|
||||
test('modulesInfo() returns info for each module', function (t) {
|
||||
var info = sequencer.modulesInfo();
|
||||
t.equal(Object.keys(info).length, Object.keys(sequencer.modules).length);
|
||||
t.equal(info.hasOwnProperty(Object.keys(sequencer.modules)[0]), true);
|
||||
@@ -49,136 +49,148 @@ test('modulesInfo() returns info for each module', function (t){
|
||||
t.end();
|
||||
});
|
||||
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){
|
||||
require('dns').resolve('www.github.com', function(err) {
|
||||
if (err) {
|
||||
console.log("Test aborted due to no internet");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImage('URL','https://ccpandhare.github.io/image-sequencer/examples/images/red.jpg', function(){
|
||||
t.equal(sequencer.images.URL.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.URL.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
}
|
||||
if (!sequencer.options.inBrowser)
|
||||
test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t) {
|
||||
require('dns').resolve('www.github.com', function (err) {
|
||||
if (err) {
|
||||
console.log("Test aborted due to no internet");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImage('URL', 'https://ccpandhare.github.io/image-sequencer/examples/images/red.jpg', function () {
|
||||
t.equal(sequencer.images.URL.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof (sequencer.images.URL.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t){
|
||||
sequencer.loadImages('examples/images/red.jpg');
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
if (!sequencer.options.inBrowser)
|
||||
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t) {
|
||||
sequencer.loadImages('examples/images/red.jpg');
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof (sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImage works too.', function (t){
|
||||
sequencer.loadImage('test2',red);
|
||||
test('loadImage works too.', function (t) {
|
||||
sequencer.loadImage('test2', red);
|
||||
t.equal(sequencer.images.test2.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.test2.steps[0].output.src), "string", "Initial output exists");
|
||||
t.equal(typeof (sequencer.images.test2.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("image","name") adds a step', function (t) {
|
||||
sequencer.addSteps('test','green-channel');
|
||||
sequencer.addSteps('test', 'channel');
|
||||
t.equal(sequencer.images.test.steps.length, 2, "Length of steps increased")
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[1].options.description, "Displays only one color channel of an image -- default is green", "Step description shown");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("name") adds a step', function (t) {
|
||||
sequencer.addSteps('green-channel');
|
||||
sequencer.addSteps('channel');
|
||||
t.equal(sequencer.images.test.steps.length, 3, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[2].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[2].options.name, "channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps(["name"]) adds a step', function (t) {
|
||||
sequencer.addSteps(['green-channel','invert']);
|
||||
sequencer.addSteps(['channel', 'invert']);
|
||||
t.equal(sequencer.images.test.steps.length, 5, "Length of steps increased by two")
|
||||
t.equal(sequencer.images.test.steps[3].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[3].options.name, "channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[4].options.name, "invert", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("name",o) adds a step', function (t) {
|
||||
sequencer.addSteps('green-channel',{});
|
||||
sequencer.addSteps('channel', {});
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[5].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[5].options.name, "channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("image","name",o) adds a step', function (t) {
|
||||
sequencer.addSteps('test','green-channel',{});
|
||||
sequencer.addSteps('test', 'channel', {});
|
||||
t.equal(sequencer.images.test.steps.length, 7, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[6].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[6].options.name, "channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('removeSteps("image",position) removes a step', function (t) {
|
||||
sequencer.removeSteps('test',1);
|
||||
sequencer.removeSteps('test', 1);
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of steps reduced");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('removeSteps({image: [positions]}) removes steps', function (t) {
|
||||
sequencer.removeSteps('test',[1,2]);
|
||||
sequencer.removeSteps('test', [1, 2]);
|
||||
t.equal(sequencer.images.test.steps.length, 4, "Length of steps reduced");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('removeSteps(position) removes steps', function (t) {
|
||||
sequencer.removeSteps([1,2]);
|
||||
sequencer.removeSteps([1, 2]);
|
||||
t.equal(sequencer.images.test.steps.length, 2, "Length of steps reduced");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps("image",position,"module",options) inserts a step', function (t) {
|
||||
sequencer.insertSteps('test',1,'green-channel',{});
|
||||
sequencer.insertSteps('test', 1, 'channel', {});
|
||||
t.equal(sequencer.images.test.steps.length, 3, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps("image",position,"module") inserts a step', function (t) {
|
||||
sequencer.insertSteps('test',1,'green-channel');
|
||||
sequencer.insertSteps('test', 1, 'channel');
|
||||
t.equal(sequencer.images.test.steps.length, 4, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps(position,"module") inserts a step', function (t) {
|
||||
sequencer.insertSteps(1,'green-channel');
|
||||
sequencer.insertSteps(1, 'channel');
|
||||
t.equal(sequencer.images.test.steps.length, 5, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps({image: {index: index, name: "module", o: options} }) inserts a step', function (t) {
|
||||
sequencer.insertSteps({test: {index:1, name:'green-channel', o:{} } });
|
||||
sequencer.insertSteps({ test: { index: 1, name: 'channel', o: {} } });
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
test('run() runs the sequencer and returns output to callback', function (t) {
|
||||
sequencer.run('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");
|
||||
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();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('replaceImage returns false in NodeJS', function (t) {
|
||||
var returnvalue = (sequencer.options.inBrowser)?false:sequencer.replaceImage("#selector","test");
|
||||
t.equal(returnvalue,false,"It does.");
|
||||
var returnvalue = (sequencer.options.inBrowser) ? false : sequencer.replaceImage("#selector", "test");
|
||||
t.equal(returnvalue, false, "It does.");
|
||||
t.end();
|
||||
});
|
||||
|
||||
var global2 = copy(false,parent);
|
||||
var global2 = copy(false, parent);
|
||||
test('No Global Variables Created', function (t) {
|
||||
t.equal(0,global2,"None Created.");
|
||||
t.equal(0, global2, "None Created.");
|
||||
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();
|
||||
});
|
||||
|
||||
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();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user