Compare commits
38 Commits
readme-dia
...
v1.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8547c60873 | ||
|
|
adc21ae843 | ||
|
|
2abf0fae09 | ||
|
|
711ae62ee3 | ||
|
|
4d75fb9640 | ||
|
|
5ac3ef008d | ||
|
|
98f913b32e | ||
|
|
c2756ffdbb | ||
|
|
d887f5eb61 | ||
|
|
3b791ad58b | ||
|
|
fba80bb151 | ||
|
|
0ceb36ffde | ||
|
|
edaa8895c7 | ||
|
|
efd5bf7624 | ||
|
|
96143564fa | ||
|
|
973ec6d6e4 | ||
|
|
801ea35393 | ||
|
|
e8ab480687 | ||
|
|
9d3e511bca | ||
|
|
d3a4c336a0 | ||
|
|
0fd797ed5a | ||
|
|
27ee18b3d2 | ||
|
|
5922b0de58 | ||
|
|
fd45d3caf3 | ||
|
|
bbef4bca57 | ||
|
|
32c5a29906 | ||
|
|
c5dee8aef3 | ||
|
|
071aa68de7 | ||
|
|
87fa166595 | ||
|
|
a5d5dd9f52 | ||
|
|
38accebcc3 | ||
|
|
c19a663fa2 | ||
|
|
38cf05bb9e | ||
|
|
3f99e2b44c | ||
|
|
73e5d14569 | ||
|
|
135c142ba8 | ||
|
|
14c59be19c | ||
|
|
ec10ab0b0d |
2
.gitignore
vendored
@@ -33,6 +33,8 @@ node_modules/*
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
todo.txt
|
||||
test.js
|
||||
|
||||
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
|
||||
|
||||
46
README.md
@@ -5,11 +5,14 @@ Image Sequencer
|
||||
|
||||
## Why
|
||||
|
||||
Image Sequencer is different from other image processing systems in that it's non-destructive: instead of modifying the original image, it creates a new image at each step in a sequence. This is because it:
|
||||
Image Sequencer is different from other image processing systems in that it's _non-destructive_: instead of modifying the original image, it **creates a new image at each step in a sequence**. This is because it:
|
||||
|
||||
* produces a legible trail of operations, to "show your work" for evidential, educational, or reproducibility reasons
|
||||
* makes the creation of new tools or "modules" simpler -- each must accept an input image, and produce an output image
|
||||
* allows many images to be run through the same sequence of steps
|
||||
* works identically in the browser, on Node.js, and on the commandline
|
||||
|
||||

|
||||
|
||||
It is also for prototyping some other related ideas:
|
||||
|
||||
@@ -19,9 +22,13 @@ It is also for prototyping some other related ideas:
|
||||
* cascading changes -- change an earlier step's settings, and see those changes affect later steps
|
||||
* "small modules"-based extensibility: see [Contributing](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Examples:
|
||||
## Examples
|
||||
|
||||
* [Single Image Demo](https://publiclab.github.io/image-sequencer/)
|
||||
* [Simple Demo](https://publiclab.github.io/image-sequencer/)
|
||||
|
||||
A diagram of this running 5 steps on a single sample image may help explain how it works:
|
||||
|
||||

|
||||
|
||||
## Jump to:
|
||||
|
||||
@@ -34,12 +41,15 @@ It is also for prototyping some other related ideas:
|
||||
* [Creating a User Interface](#creating-a-user-interface)
|
||||
* [Contributing](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md)
|
||||
* [Submit a Module](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md#contributing-modules)
|
||||
* [Get Demo Bookmarklet](https://publiclab.org/w/imagesequencerbookmarklet)
|
||||
|
||||
## Installation
|
||||
|
||||
This library works in the browser, in Node, and on the commandline (CLI), which we think is great.
|
||||
|
||||
### Browser
|
||||
|
||||
Just include [image-sequencer.js](https://publiclab.github.io/image-sequencer/dist/image-sequencer.js) in the Head section of your web page.
|
||||
Just include [image-sequencer.js](https://publiclab.github.io/image-sequencer/dist/image-sequencer.js) in the Head section of your web page. See the [demo here](https://publiclab.github.io/image-sequencer/)!
|
||||
|
||||
### Node (via NPM)
|
||||
|
||||
@@ -48,12 +58,19 @@ Add `image-sequencer` to your list of dependancies and run `$ npm install`
|
||||
|
||||
### CLI
|
||||
|
||||
(You should have Node.JS and NPM for this.)
|
||||
Globally install Image Sequencer:
|
||||
|
||||
```
|
||||
$ npm install image-sequencer -g
|
||||
```
|
||||
|
||||
(You should have Node.JS and NPM for this.)
|
||||
|
||||
### To run the debug script
|
||||
|
||||
```
|
||||
$ npm run debug invert
|
||||
```
|
||||
|
||||
## Quick Usage
|
||||
|
||||
@@ -84,8 +101,9 @@ Image Sequencer also provides a CLI for applying operations to local files. The
|
||||
|
||||
-i | --image [PATH/URL] | Input image URL. (required)
|
||||
-s | --step [step-name] | Name of the step to be added. (required)
|
||||
-b | --basic | Basic mode only outputs the final image
|
||||
-o | --output [PATH] | Directory where output will be stored. (optional)
|
||||
-op | --opions {object} | Options for the step. (optional)
|
||||
-c | --config {object} | Options for the step. (optional)
|
||||
|
||||
The basic format for using the CLI is as follows:
|
||||
|
||||
@@ -103,6 +121,22 @@ The CLI also can take multiple steps at once, like so:
|
||||
|
||||
But for this, double quotes must wrap the space-separated steps.
|
||||
|
||||
Options for the steps can be passed in one line as json in the details option like
|
||||
```
|
||||
$ ./index.js -i [PATH] -s "brightness" -d '{"brightness":50}'
|
||||
|
||||
```
|
||||
Or the values can be given through terminal prompt like
|
||||
|
||||
<img width="1436" alt="screen shot 2018-02-14 at 5 18 50 pm" src="https://user-images.githubusercontent.com/25617855/36202790-3c6e8204-11ab-11e8-9e17-7f3387ab0158.png">
|
||||
|
||||
|
||||
The CLI is also chainable with other commands using `&&`
|
||||
|
||||
```
|
||||
sequencer -i <Image Path> -s <steps> && mv <Output Image Path> <New path>
|
||||
```
|
||||
|
||||
## Classic Usage
|
||||
|
||||
### Initializing the Sequencer
|
||||
|
||||
20526
dist/image-sequencer.js
vendored
2
dist/image-sequencer.min.js
vendored
@@ -45,7 +45,7 @@ h1 {
|
||||
}
|
||||
|
||||
#dropzone input {
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step {
|
||||
@@ -76,6 +76,15 @@ h1 {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.load {
|
||||
padding: 30px;
|
||||
background: #eee;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#addStep {
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
|
||||
113
examples/demo.js
@@ -39,45 +39,64 @@ window.onload = function() {
|
||||
<p><i>'+(step.description || '')+'</i></p>\
|
||||
</div>\
|
||||
<div class="col-md-8">\
|
||||
<img alt="" class="img-thumbnail"/>\
|
||||
<div class="load" style="display:none;"><i class="fa fa-circle-o-notch fa-spin"></i></div>\
|
||||
<a><img alt="" class=“img-thumbnail” /></a>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn-xs btn-default">\
|
||||
<button confirm="Are you sure?" class="remove btn btn btn-default">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
</div>';
|
||||
|
||||
step.ui = parser.parseFromString(step.ui,'text/html');
|
||||
step.ui = step.ui.querySelector('div.row');
|
||||
step.imgElement = step.ui.querySelector('img');
|
||||
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 io = Object.assign(inputs, outputs);
|
||||
for (var i in io) {
|
||||
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', i);
|
||||
div.setAttribute('name', paramName);
|
||||
var description = inputs[paramName].desc || paramName;
|
||||
div.innerHTML = "<div class='det'>\
|
||||
<label for='" + i + "'>" + i + "</label>\
|
||||
<input name=" + i + " class='form-control' style='width:50%' type='text' />\
|
||||
<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>");
|
||||
|
||||
// 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').each(function(i, input) {
|
||||
function saveOptions() {
|
||||
$(step.ui.querySelector('div.details')).find('input,select').each(function(i, input) {
|
||||
step.options[$(input).attr('name')] = input.value;
|
||||
});
|
||||
sequencer.run();
|
||||
});
|
||||
}
|
||||
|
||||
// on clicking Save in the details pane of the step
|
||||
$(step.ui.querySelector('div.details .btn-save')).click(saveOptions);
|
||||
}
|
||||
|
||||
if(step.name != "load-image")
|
||||
@@ -89,18 +108,39 @@ window.onload = function() {
|
||||
steps.appendChild(step.ui);
|
||||
},
|
||||
|
||||
onDraw: function(step) {
|
||||
$(step.ui.querySelector('.load')).show();
|
||||
$(step.ui.querySelector('img')).hide();
|
||||
},
|
||||
|
||||
onComplete: function(step) {
|
||||
$(step.ui.querySelector('.load')).hide();
|
||||
$(step.ui.querySelector('img')).show();
|
||||
|
||||
step.imgElement.src = step.output;
|
||||
step.linkElement.href = step.output;
|
||||
|
||||
function fileExtension(output) {
|
||||
return output.split('/')[1].split(';')[0];
|
||||
}
|
||||
|
||||
step.linkElement.download = step.name + "." + fileExtension(step.output);
|
||||
step.linkElement.target = "_blank";
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
for (var i in inputs) {
|
||||
if (step.options[i] !== undefined) step.ui.querySelector('div[name="'+i+'"] input')
|
||||
.value = step.options[i];
|
||||
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];
|
||||
.value = step[i];
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -111,50 +151,39 @@ window.onload = function() {
|
||||
|
||||
});
|
||||
|
||||
sequencer.loadImage('images/tulips.png', function loadImageUI(){
|
||||
sequencer.loadImage('images/tulips.png', function loadImageUI() {
|
||||
|
||||
// look up needed steps from Url Hash:
|
||||
var stepsFromHash = getUrlHashParameter('steps').split(',')
|
||||
stepsFromHash.forEach(function eachStep(step) {
|
||||
sequencer.addSteps(step)
|
||||
});
|
||||
sequencer.run();
|
||||
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) {
|
||||
$('#options').append(
|
||||
'<div class="row">\
|
||||
<div class="col-md-5 labels">\
|
||||
'+input+':\
|
||||
</div>\
|
||||
<div class="col-md-5">\
|
||||
<input class="form-control" type="text" name="'+input+'" value="" placeholder="'+
|
||||
modulesInfo[m].inputs[input].default+'"/>\
|
||||
</div>\
|
||||
</div>'
|
||||
);
|
||||
}
|
||||
$('#addStep .info').html(sequencer.modulesInfo(m).description);
|
||||
}
|
||||
|
||||
|
||||
function addStepUI() {
|
||||
var options = {};
|
||||
var inputs = $('#options input');
|
||||
var inputs = $('#options input, #options select');
|
||||
$.each(inputs, function() {
|
||||
options[this.name] = $(this).val();
|
||||
});
|
||||
if($('#addStep select').val()=="none") return;
|
||||
if($('#addStep select').val() == "none") return;
|
||||
// add to URL hash too
|
||||
setUrlHashParameter('steps', getUrlHashParameter('steps') + ',' + $('#addStep select').val())
|
||||
var hash = getUrlHashParameter('steps') || '';
|
||||
if (hash != '') hash += ',';
|
||||
setUrlHashParameter('steps', hash + $('#addStep select').val())
|
||||
sequencer.addSteps($('#addStep select').val(),options).run();
|
||||
}
|
||||
|
||||
|
||||
BIN
examples/images/diagram-6-steps.png
Normal file
|
After Width: | Height: | Size: 444 KiB |
BIN
examples/images/diagram-workflows.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
examples/images/load.gif
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 489 KiB |
@@ -7,6 +7,8 @@
|
||||
|
||||
<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">
|
||||
|
||||
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
@@ -30,6 +32,10 @@
|
||||
|
||||
<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>
|
||||
@@ -42,18 +48,20 @@
|
||||
|
||||
<section id="steps" class="row"></section>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ function setupFileHandling(_sequencer, dropzoneId, fileInputId) {
|
||||
fileInputId = fileInputId || "fileInput";
|
||||
var fileInput = $('#' + fileInputId);
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
function handleFile(e) {
|
||||
|
||||
e.preventDefault();
|
||||
@@ -15,6 +17,8 @@ function setupFileHandling(_sequencer, dropzoneId, fileInputId) {
|
||||
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;
|
||||
|
||||
BIN
icons/ic_144.png
Executable file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
icons/ic_192.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/ic_96.png
Executable file
|
After Width: | Height: | Size: 5.3 KiB |
144
index.js
@@ -2,6 +2,7 @@
|
||||
|
||||
require('./src/ImageSequencer');
|
||||
sequencer = ImageSequencer({ui: false});
|
||||
var Spinner = require('ora')
|
||||
|
||||
var program = require('commander');
|
||||
var readlineSync = require('readline-sync');
|
||||
@@ -12,14 +13,16 @@ 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('-op, --opions {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")
|
||||
program.step = program.step.split(" ");
|
||||
|
||||
// User must input an image.
|
||||
@@ -32,71 +35,104 @@ require('fs').access(program.image, function(err){
|
||||
|
||||
// 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.");
|
||||
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]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Finally, if everything is alright, load the image, add the steps and run the sequencer.
|
||||
sequencer.loadImages(program.image,function(){
|
||||
|
||||
// Iterate through the steps and retrieve their inputs.
|
||||
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) {
|
||||
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")
|
||||
|
||||
// Iterate through the steps and retrieve their inputs.
|
||||
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 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);
|
||||
});
|
||||
|
||||
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`);
|
||||
program.config = false;
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
if(program.config && validateConfig(program.config,options)){
|
||||
console.log("Now using Options object");
|
||||
Object.keys(options).forEach(function (input) {
|
||||
options[input] = program.config[input];
|
||||
})
|
||||
}
|
||||
else{
|
||||
// If inputs exist, iterate through them and prompt for values.
|
||||
Object.keys(options).forEach(function(input) {
|
||||
var value = readlineSync.question("[" + step + "]: Enter a value for " + input + " (" + options[input].type + ", default: " + options[input].default + "): ");
|
||||
options[input] = value;
|
||||
});
|
||||
}
|
||||
// Add the step and its inputs to the sequencer.
|
||||
sequencer.addSteps(step, options);
|
||||
});
|
||||
|
||||
var spinnerObj = Spinner('Your Image is being processed..').start();
|
||||
|
||||
// Run the sequencer.
|
||||
sequencer.run(spinnerObj,function(){
|
||||
|
||||
// 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 + "): ");
|
||||
options[input] = value;
|
||||
// Export all images or final image as binary files.
|
||||
sequencer.exportBin(program.output,program.basic);
|
||||
|
||||
//check if spinner was not overriden stop it
|
||||
if(!spinnerObj.overrideFlag) {
|
||||
spinnerObj.succeed()
|
||||
console.log(`\nDone!!`)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Add the step and its inputs to the sequencer.
|
||||
sequencer.addSteps(step, options);
|
||||
|
||||
});
|
||||
|
||||
// Run the sequencer.
|
||||
sequencer.run(function(){
|
||||
|
||||
// Export all images as binary files.
|
||||
sequencer.exportBin(program.output);
|
||||
|
||||
console.log("Files will be exported to \""+program.output+"\"");
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Takes an array of steps and checks if they are valid steps for the sequencer.
|
||||
function validateSteps(steps) {
|
||||
|
||||
|
||||
// Assume all are valid in the beginning.
|
||||
var valid = true;
|
||||
steps.forEach(function(step) {
|
||||
@@ -105,7 +141,25 @@ 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_){
|
||||
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;
|
||||
}
|
||||
16
package.json
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "image-sequencer",
|
||||
"version": "0.1.1",
|
||||
"version": "1.3.0",
|
||||
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\""
|
||||
"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\""
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,10 +26,14 @@
|
||||
"commander": "^2.11.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"fisheyegl": "^0.1.2",
|
||||
"get-pixels": "~3.3.0",
|
||||
"font-awesome": "~4.5.0",
|
||||
"get-pixels": "~3.3.0",
|
||||
"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",
|
||||
"urify": "^2.1.0"
|
||||
@@ -39,7 +44,7 @@
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-uglify": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony",
|
||||
"grunt-contrib-uglify-es": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"image-filter-core": "~1.0.0",
|
||||
"image-filter-threshold": "~1.0.0",
|
||||
@@ -47,7 +52,8 @@
|
||||
"matchdep": "^0.3.0",
|
||||
"tap-spec": "^4.1.1",
|
||||
"tape": ">=4.7.0",
|
||||
"tape-run": "^3.0.0"
|
||||
"tape-run": "^3.0.0",
|
||||
"uglify-es": "^3.3.7"
|
||||
},
|
||||
"homepage": "https://github.com/publiclab/image-sequencer",
|
||||
"bin": {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// add steps to the sequencer
|
||||
function AddStep(ref, image, name, o) {
|
||||
|
||||
function addStep(image, name, o_) {
|
||||
var moduleInfo = ref.modules[name][1];
|
||||
|
||||
var o = ref.copy(o_);
|
||||
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o_.name || name;
|
||||
o.number = ref.options.sequencerCounter++; // gives a unique ID to each step
|
||||
o.name = o_.name || name || moduleInfo.name;
|
||||
o.description = o_.description || moduleInfo.description;
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.image = image;
|
||||
@@ -11,6 +15,7 @@ function AddStep(ref, image, name, o) {
|
||||
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
|
||||
21
src/CliUtils.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const fs = require('fs')
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* This function checks if the directory exists, if not it creates one on the given path
|
||||
* Callback is called with argument error if an error is encountered
|
||||
*/
|
||||
function makedir(path,callback){
|
||||
fs.access(path,function(err){
|
||||
if(err) fs.mkdir(path,function(err){
|
||||
if(err) callback(err);
|
||||
callback();
|
||||
});
|
||||
else callback()
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = exports = {
|
||||
makedir: makedir
|
||||
}
|
||||
26
src/ExportBin.js
Normal file → Executable file
@@ -2,7 +2,7 @@ var fs = require('fs');
|
||||
var getDirectories = function(rootDir, cb) {
|
||||
fs.readdir(rootDir, function(err, files) {
|
||||
var dirs = [];
|
||||
if(typeof(files)=="undefined") {
|
||||
if(typeof(files)=="undefined" || files.length == 0) {
|
||||
cb(dirs);
|
||||
return [];
|
||||
}
|
||||
@@ -23,11 +23,11 @@ var getDirectories = function(rootDir, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function ExportBin(dir = "./output/",ref) {
|
||||
module.exports = function ExportBin(dir = "./output/",ref,basic) {
|
||||
dir = (dir[dir.length-1]=="/") ? dir : dir + "/";
|
||||
if(ref.options.inBrowser) return false;
|
||||
fs.access(dir, function(err){
|
||||
if(err) fs.mkdir(dir, function() {});
|
||||
if(err) console.error(err)
|
||||
});
|
||||
getDirectories(dir,function(dirs){
|
||||
var num = 1;
|
||||
@@ -40,13 +40,19 @@ module.exports = function ExportBin(dir = "./output/",ref) {
|
||||
var root = dir+'sequencer'+num+'/';
|
||||
for(var image in ref.images) {
|
||||
var steps = ref.images[image].steps;
|
||||
for(var i in steps) {
|
||||
var datauri = steps[i].output.src;
|
||||
var ext = steps[i].output.format;
|
||||
var buffer = require('data-uri-to-buffer')(datauri);
|
||||
fs.writeFile(root+image+"_"+i+"."+ext,buffer,function(){
|
||||
|
||||
});
|
||||
if(basic){
|
||||
var datauri = steps.slice(-1)[0].output.src;
|
||||
var ext = steps.slice(-1)[0].output.format;
|
||||
var buffer = require('data-uri-to-buffer')(datauri);
|
||||
fs.writeFile(root+image+"_"+(steps.length-1)+"."+ext,buffer,function(){});
|
||||
}
|
||||
else{
|
||||
for(var i in steps) {
|
||||
var datauri = steps[i].output.src;
|
||||
var ext = steps[i].output.format;
|
||||
var buffer = require('data-uri-to-buffer')(datauri);
|
||||
fs.writeFile(root+image+"_"+i+"."+ext,buffer,function(){});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,23 +2,22 @@ if (typeof window !== 'undefined') {window.$ = window.jQuery = require('jquery')
|
||||
else {var isBrowser = false}
|
||||
|
||||
ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
|
||||
options = options || {};
|
||||
options.inBrowser = options.inBrowser || isBrowser;
|
||||
// if (options.inBrowser) options.ui = options.ui || require('./UserInterface');
|
||||
options.sequencerCounter = 0;
|
||||
|
||||
|
||||
function objTypeOf(object){
|
||||
return Object.prototype.toString.call(object).split(" ")[1].slice(0,-1)
|
||||
}
|
||||
|
||||
|
||||
function log(color,msg) {
|
||||
if(options.ui!="none") {
|
||||
if(arguments.length==1) console.log(arguments[0]);
|
||||
else if(arguments.length==2) console.log(color,msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function copy(a) {
|
||||
if (!typeof(a) == "object") return a;
|
||||
if (objTypeOf(a) == "Array") return a.slice();
|
||||
@@ -31,40 +30,40 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
function makeArray(input) {
|
||||
return (objTypeOf(input)=="Array")?input:[input];
|
||||
}
|
||||
|
||||
|
||||
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');
|
||||
// else if (options.imageUrl) loadImage(imageUrl);
|
||||
|
||||
|
||||
function addSteps(){
|
||||
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
var json_q = {};
|
||||
for(var arg in arguments){args.push(copy(arguments[arg]));}
|
||||
json_q = formatInput.call(this_,args,"+");
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
function removeStep(image,index) {
|
||||
//remove the step from images[image].steps and redraw remaining images
|
||||
if(index>0) {
|
||||
@@ -74,73 +73,78 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
}
|
||||
//tell the UI a step has been removed
|
||||
}
|
||||
|
||||
|
||||
function removeSteps(image,index) {
|
||||
var run = {}, indices;
|
||||
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 json_q = formatInput.call(this_,args,"-");
|
||||
inputlog.push({method:"removeSteps", json_q:copy(json_q)});
|
||||
|
||||
|
||||
for (var img in json_q) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
function insertSteps(image, index, name, o) {
|
||||
var run = {};
|
||||
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
for (var arg in arguments) args.push(arguments[arg]);
|
||||
|
||||
|
||||
var json_q = formatInput.call(this_,args,"^");
|
||||
inputlog.push({method:"insertSteps", json_q:copy(json_q)});
|
||||
|
||||
|
||||
for (var img in json_q) {
|
||||
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(spinnerObj,t_image,t_from) {
|
||||
let progressObj;
|
||||
if(arguments[0] != 'test'){
|
||||
progressObj = spinnerObj
|
||||
delete arguments['0']
|
||||
}
|
||||
|
||||
function run(t_image,t_from) {
|
||||
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,progressObj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function loadImages() {
|
||||
var args = [];
|
||||
var sequencer = this;
|
||||
for (var arg in arguments) args.push(copy(arguments[arg]));
|
||||
var json_q = formatInput.call(this,args,"l");
|
||||
|
||||
|
||||
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",
|
||||
sequencer: this,
|
||||
@@ -152,45 +156,46 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
setUI: this.setUI,
|
||||
images: loadedimages
|
||||
};
|
||||
|
||||
|
||||
function load(i) {
|
||||
if(i==loadedimages.length) {
|
||||
json_q.callback.call(ret);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
load(0);
|
||||
}
|
||||
|
||||
|
||||
function replaceImage(selector,steps,options) {
|
||||
options = options || {};
|
||||
return require('./ReplaceImage')(this,selector,steps);
|
||||
options.callback = options.callback || function() {};
|
||||
return require('./ReplaceImage')(this,selector,steps,options);
|
||||
}
|
||||
|
||||
|
||||
function setUI(UI) {
|
||||
this.events = require('./UserInterface')(UI);
|
||||
this.events = require('./ui/UserInterface')(UI);
|
||||
}
|
||||
|
||||
var exportBin = function(dir) {
|
||||
return require('./ExportBin')(dir,this);
|
||||
|
||||
var exportBin = function(dir,basic) {
|
||||
return require('./ExportBin')(dir,this,basic);
|
||||
}
|
||||
|
||||
|
||||
function modulesInfo(name) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
//literals and objects
|
||||
name: "ImageSequencer",
|
||||
@@ -199,7 +204,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
modules: modules,
|
||||
images: images,
|
||||
events: events,
|
||||
|
||||
|
||||
//user functions
|
||||
loadImages: loadImages,
|
||||
loadImage: loadImages,
|
||||
@@ -211,12 +216,12 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
setUI: setUI,
|
||||
exportBin: exportBin,
|
||||
modulesInfo: modulesInfo,
|
||||
|
||||
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
module.exports = ImageSequencer;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// 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_) {
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
/*
|
||||
* Core modules and their info files
|
||||
*/
|
||||
* 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')
|
||||
],
|
||||
'ndvi-red': [
|
||||
require('./modules/NdviRed/Module'),require('./modules/NdviRed/info')
|
||||
'brightness': [
|
||||
require('./modules/Brightness/Module'),require('./modules/Brightness/info')
|
||||
],
|
||||
'edge-detect':[
|
||||
require('./modules/EdgeDetect/Module'),require('./modules/EdgeDetect/info')
|
||||
],
|
||||
'ndvi': [
|
||||
require('./modules/Ndvi/Module'),require('./modules/Ndvi/info')
|
||||
],
|
||||
'invert': [
|
||||
require('./modules/Invert/Module'),require('./modules/Invert/info')
|
||||
@@ -14,8 +20,8 @@ module.exports = {
|
||||
'crop': [
|
||||
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')
|
||||
@@ -25,5 +31,11 @@ module.exports = {
|
||||
],
|
||||
'dynamic': [
|
||||
require('./modules/Dynamic/Module'),require('./modules/Dynamic/info')
|
||||
],
|
||||
'blur': [
|
||||
require('./modules/Blur/Module'),require('./modules/Blur/info')
|
||||
],
|
||||
'saturation': [
|
||||
require('./modules/Saturation/Module'),require('./modules/Saturation/info')
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// 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);
|
||||
var images = [];
|
||||
for (var i = 0; i < input.length; i++)
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (input[i] instanceof HTMLImageElement) images.push(input[i]);
|
||||
for (var i in images) {
|
||||
var the_image = images[i];
|
||||
var url = images[i].src;
|
||||
}
|
||||
|
||||
function replaceImage (img, steps) {
|
||||
var url = img.src;
|
||||
var ext = url.split('.').pop();
|
||||
|
||||
var xmlHTTP = new XMLHttpRequest();
|
||||
@@ -25,11 +29,19 @@ function ReplaceImage(ref,selector,steps,options) {
|
||||
else make(url);
|
||||
|
||||
function make(url) {
|
||||
this_.loadImage('default',url).addSteps('default',steps).run(function(out){
|
||||
the_image.src = out;
|
||||
tempSequencer.loadImage(url, function(){
|
||||
this.addSteps(steps).run({stop:function(){}},function(out){
|
||||
img.src = out;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
replaceImage(images[i],steps);
|
||||
if (i == images.length-1)
|
||||
options.callback();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReplaceImage;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
function Run(ref, json_q, callback) {
|
||||
function Run(ref, json_q, callback,progressObj) {
|
||||
if(!progressObj) progressObj = {stop: function(){}}
|
||||
|
||||
function drawStep(drawarray, pos) {
|
||||
if (pos == drawarray.length && drawarray[pos - 1] !== undefined) {
|
||||
@@ -17,7 +18,7 @@ function Run(ref, json_q, callback) {
|
||||
var input = ref.images[image].steps[i - 1].output;
|
||||
ref.images[image].steps[i].draw(ref.copy(input), function onEachStep() {
|
||||
drawStep(drawarray, ++pos);
|
||||
});
|
||||
},progressObj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
src/modules/Blur/Blur.js
Executable file
@@ -0,0 +1,85 @@
|
||||
module.exports = exports = function(pixels,blur){
|
||||
let kernel = kernelGenerator(blur,1)
|
||||
kernel = flipKernel(kernel)
|
||||
var oldpix = pixels
|
||||
|
||||
for(let i=0;i<pixels.shape[0];i++){
|
||||
for(let j=0;j<pixels.shape[1];j++){
|
||||
let neighboutPos = getNeighbouringPixelPositions([i,j])
|
||||
let acc = [0.0,0.0,0.0,0.0]
|
||||
for(let a = 0; a < kernel.length; a++){
|
||||
for(let b = 0; b < kernel.length; b++){
|
||||
acc[0] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],0) * kernel[a][b]);
|
||||
acc[1] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],1) * kernel[a][b]);
|
||||
acc[2] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],2) * kernel[a][b]);
|
||||
acc[3] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],3) * kernel[a][b]);
|
||||
}
|
||||
}
|
||||
pixels.set(i,j,0,acc[0])
|
||||
pixels.set(i,j,1,acc[1])
|
||||
pixels.set(i,j,2,acc[2])
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
|
||||
|
||||
|
||||
//Generates a 3x3 Gaussian kernel
|
||||
function kernelGenerator(sigma,size){
|
||||
|
||||
/*
|
||||
Trying out a variable radius kernel not working as of now
|
||||
*/
|
||||
// const coeff = (1.0/(2.0*Math.PI*sigma*sigma))
|
||||
// const expCoeff = -1 * (1.0/2.0 * sigma * sigma)
|
||||
// let e = Math.E
|
||||
// let result = []
|
||||
// for(let i = -1 * size;i<=size;i++){
|
||||
// let arr = []
|
||||
// for(let j= -1 * size;j<=size;j++){
|
||||
// arr.push(coeff * Math.pow(e,expCoeff * ((i * i) + (j*j))))
|
||||
// }
|
||||
// result.push(arr)
|
||||
// }
|
||||
// let sum = result.reduce((sum,val)=>{
|
||||
// return val.reduce((sumInner,valInner)=>{
|
||||
// return sumInner+valInner
|
||||
// })
|
||||
// })
|
||||
// result = result.map(arr=>arr.map(val=>(val + 0.0)/(sum + 0.0)))
|
||||
|
||||
// return result
|
||||
|
||||
return [
|
||||
[2.0/159.0,4.0/159.0,5.0/159.0,4.0/159.0,2.0/159.0],
|
||||
[4.0/159.0,9.0/159.0,12.0/159.0,9.0/159.0,4.0/159.0],
|
||||
[5.0/159.0,12.0/159.0,15.0/159.0,12.0/159.0,5.0/159.0],
|
||||
[4.0/159.0,9.0/159.0,12.0/159.0,9.0/159.0,4.0/159.0],
|
||||
[2.0/159.0,4.0/159.0,5.0/159.0,4.0/159.0,2.0/159.0]
|
||||
]
|
||||
}
|
||||
function getNeighbouringPixelPositions(pixelPosition){
|
||||
let x = pixelPosition[0],y=pixelPosition[1]
|
||||
let result = []
|
||||
for(let i=-2;i<=2;i++){
|
||||
let arr = []
|
||||
for(let j=-2;j<=2;j++){
|
||||
arr.push([x + i,y + j])
|
||||
}
|
||||
result.push(arr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function flipKernel(kernel){
|
||||
let result = []
|
||||
for(let i =kernel.length-1;i>=0;i--){
|
||||
let arr = []
|
||||
for(let j = kernel[i].length-1;j>=0;j--){
|
||||
arr.push(kernel[i][j])
|
||||
}
|
||||
result.push(arr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
59
src/modules/Blur/Module.js
Executable file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Blur an Image
|
||||
*/
|
||||
module.exports = function Blur(options,UI){
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
function extraManipulation(pixels){
|
||||
pixels = require('./Blur')(pixels,options.blur)
|
||||
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
|
||||
}
|
||||
}
|
||||
11
src/modules/Blur/info.json
Executable file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Blur",
|
||||
"description": "Gaussian blur an image by a given value, typically 0-5",
|
||||
"inputs": {
|
||||
"blur": {
|
||||
"type": "integer",
|
||||
"desc": "amount of gaussian blur(Less blur gives more detail, typically 0-5)",
|
||||
"default": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/modules/Brightness/Module.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Changes the Image Brightness
|
||||
*/
|
||||
|
||||
module.exports = function Brightness(options,UI){
|
||||
options = options || {};
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
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);
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a){
|
||||
var val = (options.brightness)/100.0
|
||||
|
||||
r = val*r<255?val*r:255
|
||||
g = val*g<255?val*g:255
|
||||
b = val*b<255?val*b:255
|
||||
return [r , g, b, a]
|
||||
}
|
||||
|
||||
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,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
11
src/modules/Brightness/info.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Brightness",
|
||||
"description": "Change the brightness of the image by given percent value",
|
||||
"inputs": {
|
||||
"brightness": {
|
||||
"type": "integer",
|
||||
"desc": "% brightness for the new image",
|
||||
"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){
|
||||
@@ -38,6 +42,7 @@ module.exports = function GreenChannel(options,UI) {
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -37,6 +39,7 @@ module.exports = function SegmentedColormap(options,UI) {
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
12
src/modules/Colormap/info.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"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",
|
||||
"desc": "Name of the Colormap",
|
||||
"default": "default",
|
||||
"values": ["default","greyscale","stretched","fastie"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
module.exports = function CropModule(options,UI) {
|
||||
|
||||
// TODO: we could also set this to {} if nil in AddModule.js to avoid this line:
|
||||
options = options || {};
|
||||
options.title = "Crop Image";
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
|
||||
@@ -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,47 +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,6 @@
|
||||
{
|
||||
"name": "Dynamic",
|
||||
"description": "A module which accepts JavaScript math expressions to produce each color channel based on the original image's color. See <a href='https://publiclab.org/wiki/infragram-sandbox'>Infragrammar</a>.",
|
||||
"inputs": {
|
||||
"red": {
|
||||
"type": "input",
|
||||
|
||||
179
src/modules/EdgeDetect/EdgeUtils.js
Normal file
@@ -0,0 +1,179 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
//define kernels for the sobel filter
|
||||
const kernelx = [[-1,0,1],[-2,0,2],[-1,0,1]],
|
||||
kernely = [[-1,-2,-1],[0,0,0],[1,2,1]]
|
||||
|
||||
let angles = []
|
||||
let mags = []
|
||||
let strongEdgePixels = []
|
||||
let weakEdgePixels = []
|
||||
let notInUI
|
||||
module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio,inBrowser){
|
||||
notInUI = !inBrowser
|
||||
for(var x = 0; x < pixels.shape[0]; x++) {
|
||||
angles.push([])
|
||||
mags.push([])
|
||||
for(var y = 0; y < pixels.shape[1]; y++) {
|
||||
var result = changePixel(
|
||||
pixels,
|
||||
pixels.get(x,y,0),
|
||||
pixels.get(x, y, 3),
|
||||
x,
|
||||
y
|
||||
)
|
||||
let pixel = result.pixel
|
||||
|
||||
pixels.set(x, y, 0, pixel[0]);
|
||||
pixels.set(x, y, 1, pixel[1]);
|
||||
pixels.set(x, y, 2, pixel[2]);
|
||||
pixels.set(x, y, 3, pixel[3]);
|
||||
|
||||
mags.slice(-1)[0].push(pixel[3])
|
||||
angles.slice(-1)[0].push(result.angle)
|
||||
}
|
||||
}
|
||||
|
||||
return hysteresis(doubleThreshold(nonMaxSupress(pixels),highThresholdRatio,lowThresholdRatio))
|
||||
}
|
||||
|
||||
//changepixel function that convolutes every pixel (sobel filter)
|
||||
function changePixel(pixels,val,a,x,y){
|
||||
let magX = 0.0
|
||||
for(let a = 0; a < 3; a++){
|
||||
for(let b = 0; b < 3; b++){
|
||||
|
||||
let xn = x + a - 1;
|
||||
let yn = y + b - 1;
|
||||
|
||||
magX += pixels.get(xn,yn,0) * kernelx[a][b];
|
||||
}
|
||||
}
|
||||
let magY = 0.0
|
||||
for(let a = 0; a < 3; a++){
|
||||
for(let b = 0; b < 3; b++){
|
||||
|
||||
let xn = x + a - 1;
|
||||
let yn = y + b - 1;
|
||||
|
||||
magY += pixels.get(xn,yn,0) * kernely[a][b];
|
||||
}
|
||||
}
|
||||
let mag = Math.sqrt(Math.pow(magX,2) + Math.pow(magY,2))
|
||||
let angle = Math.atan2(magY,magX)
|
||||
return {
|
||||
pixel:
|
||||
[val,val,val,mag],
|
||||
angle: angle
|
||||
}
|
||||
}
|
||||
|
||||
//Non Maximum Supression without interpolation
|
||||
function nonMaxSupress(pixels) {
|
||||
angles = angles.map((arr)=>arr.map(convertToDegrees))
|
||||
|
||||
for(let i = 1;i<pixels.shape[0]-1;i++){
|
||||
for(let j=1;j<pixels.shape[1]-1;j++){
|
||||
|
||||
let angle = angles[i][j]
|
||||
let pixel = pixels.get(i,j)
|
||||
|
||||
if ((angle>=-22.5 && angle<=22.5) ||
|
||||
(angle<-157.5 && angle>=-180))
|
||||
|
||||
if ((mags[i][j]>= mags[i][j+1]) &&
|
||||
(mags[i][j] >= mags[i][j-1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
|
||||
else if ((angle>=22.5 && angle<=67.5) ||
|
||||
(angle<-112.5 && angle>=-157.5))
|
||||
|
||||
if ((mags[i][j] >= mags[i+1][j+1]) &&
|
||||
(mags[i][j] >= mags[i-1][j-1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
|
||||
else if ((angle>=67.5 && angle<=112.5) ||
|
||||
(angle<-67.5 && angle>=-112.5))
|
||||
|
||||
if ((mags[i][i] >= mags[i+1][j]) &&
|
||||
(mags[i][j] >= mags[i][j]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
|
||||
else if ((angle>=112.5 && angle<=157.5) ||
|
||||
(angle<-22.5 && angle>=-67.5))
|
||||
|
||||
if ((mags[i][j] >= mags[i+1][j-1]) &&
|
||||
(mags[i][j] >= mags[i-1][j+1]))
|
||||
pixels.set(i,j,3,mags[i][j])
|
||||
else
|
||||
pixels.set(i,j,3,0)
|
||||
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
}
|
||||
//Converts radians to degrees
|
||||
var convertToDegrees = radians => (radians * 180)/Math.PI
|
||||
|
||||
//Finds the max value in a 2d array like mags
|
||||
var findMaxInMatrix = arr => Math.max(...arr.map(el=>el.map(val=>!!val?val:0)).map(el=>Math.max(...el)))
|
||||
|
||||
//Applies the double threshold to the image
|
||||
function doubleThreshold(pixels,highThresholdRatio,lowThresholdRatio){
|
||||
const highThreshold = findMaxInMatrix(mags) * 0.2
|
||||
const lowThreshold = highThreshold * lowThresholdRatio
|
||||
|
||||
for(let i =0;i<pixels.shape[0];i++){
|
||||
for(let j=0;j<pixels.shape[1];j++){
|
||||
let pixelPos = [i,j]
|
||||
|
||||
mags[i][j]>lowThreshold
|
||||
?mags[i][j]>highThreshold
|
||||
?strongEdgePixels.push(pixelPos)
|
||||
:weakEdgePixels.push(pixelPos)
|
||||
:pixels.set(i,j,3,0)
|
||||
}
|
||||
}
|
||||
|
||||
strongEdgePixels.forEach(pix=>pixels.set(pix[0],pix[1],3,255))
|
||||
|
||||
return pixels
|
||||
}
|
||||
|
||||
// hysteresis edge tracking algorithm
|
||||
function hysteresis(pixels){
|
||||
function getNeighbouringPixelPositions(pixelPosition){
|
||||
let x = pixelPosition[0],y=pixelPosition[1]
|
||||
return [[x+1,y+1],
|
||||
[x+1,y],
|
||||
[x+1,y-1],
|
||||
[x,y+1],
|
||||
[x,y-1],
|
||||
[x-1,y+1],
|
||||
[x-1,y],
|
||||
[x-1,y-1]]
|
||||
}
|
||||
|
||||
//This can potentially be improved see https://en.wikipedia.org/wiki/Connected-component_labeling
|
||||
for(weakPixel in weakEdgePixels){
|
||||
let neighbourPixels = getNeighbouringPixelPositions(weakEdgePixels[weakPixel])
|
||||
for(pixel in neighbourPixels){
|
||||
if(strongEdgePixels.find(el=> _.isEqual(el,neighbourPixels[pixel]))) {
|
||||
pixels.set(weakPixel[0],weakPixel[1],3,255)
|
||||
weakEdgePixels.splice(weakPixel,weakPixel)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
weakEdgePixels.forEach(pix=>pixels.set(pix[0],pix[1],3,0))
|
||||
return pixels
|
||||
}
|
||||
|
||||
|
||||
|
||||
67
src/modules/EdgeDetect/Module.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Detect Edges in an Image
|
||||
*/
|
||||
module.exports = function edgeDetect(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
options.highThresholdRatio = options.highThresholdRatio||0.2
|
||||
options.lowThresholdRatio = options.lowThresholdRatio||0.15
|
||||
|
||||
// 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,progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
function extraManipulation(pixels){
|
||||
pixels = require('ndarray-gaussian-filter')(pixels,options.blur)
|
||||
return require('./EdgeUtils')(pixels,options.highThresholdRatio,options.lowThresholdRatio,options.inBrowser)
|
||||
}
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r+g+b)/3, (r+g+b)/3, (r+g+b)/3, a];
|
||||
}
|
||||
|
||||
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,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
21
src/modules/EdgeDetect/info.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Detect Edges",
|
||||
"description": "this module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. Read more at: https://en.wikipedia.org/wiki/Canny_edge_detector",
|
||||
"inputs": {
|
||||
"blur": {
|
||||
"type": "integer",
|
||||
"desc": "amount of gaussian blur(Less blur gives more detail, typically 0-5)",
|
||||
"default": 2
|
||||
},
|
||||
"highThresholdRatio":{
|
||||
"type": "float",
|
||||
"desc": "The high threshold ratio for the image",
|
||||
"default": 0.2
|
||||
},
|
||||
"lowThresholdRatio": {
|
||||
"type": "float",
|
||||
"desc": "The low threshold value for the image",
|
||||
"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": {
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,16 @@
|
||||
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);
|
||||
|
||||
@@ -40,6 +40,7 @@ module.exports = function Invert(options,UI) {
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -40,6 +44,7 @@ module.exports = function NdviRed(options,UI) {
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
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": {
|
||||
}
|
||||
}
|
||||
67
src/modules/Saturation/Module.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Saturate an image with a value from 0 to 1
|
||||
*/
|
||||
module.exports = function Saturation(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
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) {
|
||||
|
||||
var cR = 0.299;
|
||||
var cG = 0.587;
|
||||
var cB = 0.114;
|
||||
|
||||
var p = Math.sqrt((cR * (r*r)) + (cG * (g*g)) + (cB * (g*g)));
|
||||
|
||||
r = p+(r-p)*(options.saturation);
|
||||
g = p+(g-p)*(options.saturation);
|
||||
b = p+(b-p)*(options.saturation);
|
||||
|
||||
|
||||
return [Math.round(r), Math.round(g), Math.round(b), a];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step ahs been drawn
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
//setup: setup, // optional
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
11
src/modules/Saturation/info.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Saturation",
|
||||
"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 highly saturated",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "Segmented Colormap",
|
||||
"inputs": {
|
||||
"colormap": {
|
||||
"type": "select",
|
||||
"desc": "Name of the Colormap",
|
||||
"default": "default",
|
||||
"values": ["default","greyscale","stretched","fastie"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,80 @@
|
||||
/*
|
||||
* General purpose per-pixel manipulation
|
||||
* accepting a changePixel() method to remix a pixel's channels
|
||||
*/
|
||||
* General purpose per-pixel manipulation
|
||||
* accepting a changePixel() method to remix a pixel's channels
|
||||
*/
|
||||
module.exports = function PixelManipulation(image, options) {
|
||||
|
||||
|
||||
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');
|
||||
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
getPixels(image.src, function(err, pixels) {
|
||||
|
||||
|
||||
if(err) {
|
||||
console.log('Bad image path');
|
||||
console.log('Bad image path', image);
|
||||
return;
|
||||
}
|
||||
|
||||
if(options.getNeighbourPixel){
|
||||
options.getNeighbourPixel.fun = function (distX,distY) {
|
||||
return options.getNeighbourPixel(pixels,x,y,distX,distY);
|
||||
};
|
||||
}
|
||||
|
||||
// iterate through pixels;
|
||||
// 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){
|
||||
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, 0),
|
||||
pixels.get(x, y, 1),
|
||||
pixels.get(x, y, 2),
|
||||
pixels.get(x, y, 3)
|
||||
);
|
||||
|
||||
pixels.set(x, y, 0, pixel[0]);
|
||||
pixels.set(x, y, 1, pixel[1]);
|
||||
pixels.set(x, y, 2, pixel[2]);
|
||||
pixels.set(x, y, 3, pixel[3]);
|
||||
|
||||
|
||||
if(!options.inBrowser)
|
||||
pace.op()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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){
|
||||
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;
|
||||
|
||||
@@ -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,
|
||||
13
test/cli.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const cliUtils = require('../src/CliUtils');
|
||||
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()
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ var test = require('tape');
|
||||
// We should only test headless code here.
|
||||
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
|
||||
|
||||
require('../src/ImageSequencer.js');
|
||||
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";
|
||||
@@ -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();
|
||||
});
|
||||
@@ -7,7 +7,7 @@ var DataURItoBuffer = require('data-uri-to-buffer');
|
||||
// We should only test headless code here.
|
||||
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
|
||||
|
||||
require('../src/ImageSequencer.js');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
//require image files as DataURLs so they can be tested alike on browser and Node.
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
@@ -15,12 +15,13 @@ var sequencer = ImageSequencer({ ui: false });
|
||||
var qr = require('./images/IS-QR.js');
|
||||
var test_png = require('./images/test.png.js');
|
||||
var test_gif = require('./images/test.gif.js');
|
||||
var spinner = require('ora')('').start()
|
||||
|
||||
sequencer.loadImages(test_png);
|
||||
sequencer.addSteps(['invert','invert']);
|
||||
|
||||
test("Preload", function(t) {
|
||||
sequencer.run(function(){
|
||||
sequencer.run(spinner,function(){
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@@ -51,7 +52,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(spinner.start(),function(){
|
||||
t.end();
|
||||
});
|
||||
})
|
||||
@@ -64,7 +65,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(spinner.start(),function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
@@ -73,9 +74,10 @@ 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(spinner,function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
spinner.stop(true)
|
||||
@@ -6,7 +6,7 @@ var test = require('tape');
|
||||
// We should only test headless code here.
|
||||
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
|
||||
|
||||
require('../src/ImageSequencer.js');
|
||||
require('../../src/ImageSequencer.js');
|
||||
|
||||
// This function is used to test whether or not any additional global variables are being created
|
||||
function copy(g,a) {
|
||||
@@ -42,7 +42,6 @@ test('loadImages loads a DataURL image and creates a step.', function (t){
|
||||
|
||||
test('modulesInfo() returns info for each module', function (t){
|
||||
var info = sequencer.modulesInfo();
|
||||
t.equal(Object.keys(info).length, 8);
|
||||
t.equal(Object.keys(info).length, Object.keys(sequencer.modules).length);
|
||||
t.equal(info.hasOwnProperty(Object.keys(sequencer.modules)[0]), true);
|
||||
t.equal(info[Object.keys(sequencer.modules)[0]].hasOwnProperty('name'), true);
|
||||
@@ -83,38 +82,39 @@ test('loadImage works too.', function (t){
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -137,30 +137,30 @@ test('removeSteps(position) removes steps', function (t) {
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
25
test/modules/replace.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
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";
|
||||
|
||||
if(typeof(document) !== "undefined") {
|
||||
var image = document.createElement("img");
|
||||
image.src = red;
|
||||
document.body.appendChild(image);
|
||||
}
|
||||
|
||||
test('replaceImage works.', function (t){
|
||||
if (typeof(document) === "undefined")
|
||||
t.end();
|
||||
|
||||
sequencer.replaceImage("img","invert",{ callback: function(){
|
||||
t.equal(0,0, "replaceImage works");
|
||||
t.end();
|
||||
} });
|
||||
});
|
||||