Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
991e9bb29c | ||
|
|
cbbfbff4bc | ||
|
|
768e2ce95d | ||
|
|
51524f747a | ||
|
|
b16b45afa8 | ||
|
|
39418da187 | ||
|
|
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 | ||
|
|
1c7f59cbfd | ||
|
|
bef53ef1a2 | ||
|
|
1c7f7c15af | ||
|
|
7cbf793655 | ||
|
|
829e19e58d | ||
|
|
ca6910017d | ||
|
|
4e780f3a91 | ||
|
|
3a4e4f9575 | ||
|
|
6420a70aad | ||
|
|
99ec91b916 | ||
|
|
4d9a4bcaa1 | ||
|
|
0e50a1b2f5 | ||
|
|
376c264168 | ||
|
|
1f3000b7f9 | ||
|
|
dc9827049a | ||
|
|
f69f8461a7 | ||
|
|
bde6dbed8f | ||
|
|
4fb1ecce86 | ||
|
|
5b860dee83 | ||
|
|
959f5db6df | ||
|
|
237a96247a | ||
|
|
2e4255a5ca | ||
|
|
fe3ec335fe | ||
|
|
eafc639555 | ||
|
|
7da59aed0c | ||
|
|
d759680869 | ||
|
|
4c662d908e | ||
|
|
c5d62e5529 | ||
|
|
d466afb90d | ||
|
|
72435eb9a4 | ||
|
|
c7e4ec7c4a | ||
|
|
c28c958414 | ||
|
|
38975791ca | ||
|
|
dbc0ad6e69 | ||
|
|
c3c6713ec1 | ||
|
|
873492a456 | ||
|
|
ca044b1b01 | ||
|
|
0fa0684076 | ||
|
|
c655b62813 | ||
|
|
69c656fbd5 | ||
|
|
ee467f2a7b | ||
|
|
21796bb58c | ||
|
|
cc308748c8 | ||
|
|
0095462384 | ||
|
|
54c7393417 | ||
|
|
555ea9b945 | ||
|
|
f879f31e4f | ||
|
|
782e60269e | ||
|
|
963129d513 | ||
|
|
d207c147fe | ||
|
|
a9e0068628 | ||
|
|
5b7e47dd79 | ||
|
|
95ffa0453b | ||
|
|
76ffb6a013 | ||
|
|
78fb43a8af | ||
|
|
04e2ee7903 | ||
|
|
06af6007a2 | ||
|
|
42425bcf69 | ||
|
|
3b73c6d700 | ||
|
|
889c751399 | ||
|
|
26538891a3 | ||
|
|
1510043a50 | ||
|
|
d34130b791 | ||
|
|
8579cfafa7 | ||
|
|
2f117c1f35 | ||
|
|
14b1152e5a | ||
|
|
c90bf3fa0a | ||
|
|
802648ae69 | ||
|
|
812cd808c7 | ||
|
|
45328999f3 | ||
|
|
88f31c9665 | ||
|
|
235b2428ee | ||
|
|
3354071548 | ||
|
|
02ec74bad3 | ||
|
|
b8ec24c185 | ||
|
|
187f66429a | ||
|
|
8f5e9f0c40 | ||
|
|
e6f1ff3243 | ||
|
|
b9797a0329 | ||
|
|
61bd95355b | ||
|
|
9208216f99 | ||
|
|
3421714c2b | ||
|
|
93941b1280 | ||
|
|
6eea7b8bd6 | ||
|
|
d1b47a73fe | ||
|
|
31e9b3ec9d | ||
|
|
1b7dadf41f | ||
|
|
f1f3574468 | ||
|
|
8610ab1ff8 | ||
|
|
2888c2d735 | ||
|
|
53a8963b72 | ||
|
|
6e136e6c2f | ||
|
|
467fe1d2e5 | ||
|
|
b59383ce53 | ||
|
|
b1c9949757 | ||
|
|
f3f3f5f615 | ||
|
|
7f17f825ab | ||
|
|
ec4459309b | ||
|
|
d80a587bf3 | ||
|
|
d7eb5a2f0c | ||
|
|
03d9ff1ea2 | ||
|
|
2e284560e2 | ||
|
|
f5c21e7c82 | ||
|
|
fab35c5efb | ||
|
|
29cb36dd31 | ||
|
|
d90c61f85d | ||
|
|
21334ff5d8 | ||
|
|
1117279d44 | ||
|
|
6baed92ec6 | ||
|
|
e9f045f5cd | ||
|
|
ec657a6735 | ||
|
|
246130c8e6 | ||
|
|
82bb274cfb | ||
|
|
78beae48d9 | ||
|
|
7584b1ec45 | ||
|
|
cfdc4c509e | ||
|
|
867792927e | ||
|
|
555e37190b | ||
|
|
a74f67ef06 | ||
|
|
b963dfd297 | ||
|
|
10dadeb63b | ||
|
|
c4c1f5bd2d | ||
|
|
86d475d3e7 | ||
|
|
52e8e652c3 | ||
|
|
73930849a7 | ||
|
|
dc9b9bcde3 | ||
|
|
dd595f371c | ||
|
|
1404d455fc | ||
|
|
5994963253 | ||
|
|
e6a96d723d | ||
|
|
43d55d2781 | ||
|
|
8d8cf33d23 | ||
|
|
09c0a180ec | ||
|
|
51e6c70fc8 | ||
|
|
41c392d838 | ||
|
|
368d11230c | ||
|
|
a4e7470a5e | ||
|
|
3112816afc | ||
|
|
2eb41b20f9 | ||
|
|
ec6cc396e4 | ||
|
|
10dc6c0a7c | ||
|
|
f6225422b1 | ||
|
|
5dfbc9d28b | ||
|
|
74eb686604 | ||
|
|
fedccd59a1 | ||
|
|
f1551abb7f | ||
|
|
7c1228fca9 | ||
|
|
1bf58351b4 |
8
.gitignore
vendored
@@ -25,6 +25,7 @@ build/Release
|
||||
|
||||
# Dependency directory
|
||||
node_modules
|
||||
node_modules/*
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
@@ -32,5 +33,12 @@ node_modules
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
todo.txt
|
||||
test.js
|
||||
output.txt
|
||||
output/
|
||||
node_modules/
|
||||
node_modules/*
|
||||
|
||||
219
CONTRIBUTING.md
Normal file
@@ -0,0 +1,219 @@
|
||||
Contributing to Image Sequencer
|
||||
===
|
||||
|
||||
Happily accepting pull requests; to edit the core library, modify files in `./src/`. To build, run `npm install` followed by `grunt build`.
|
||||
|
||||
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
|
||||
|
||||
## Jump To
|
||||
|
||||
* [README.md](https://github.com/publiclab/image-sequencer)
|
||||
* [Contributing Modules](#contributing-modules)
|
||||
* [Info File](#info-file)
|
||||
* [Ideas](#ideas)
|
||||
|
||||
****
|
||||
|
||||
## Contribution ideas
|
||||
|
||||
See [this issue](https://github.com/publiclab/image-sequencer/issues/118) for a range of ideas for new contributions, and links to possibly helpful libraries. Also see the [new features issues list](https://github.com/publiclab/image-sequencer/labels/new-feature).
|
||||
|
||||
### Bugs
|
||||
|
||||
If you find a bug please list it here, and help us develop Image Sequencer by [opening an issue](https://github.com/publiclab/image-sequencer/issues/new)!
|
||||
|
||||
****
|
||||
|
||||
## Contributing modules
|
||||
|
||||
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 || {};
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
UI.onDraw(options.step); // tell the UI to "draw"
|
||||
|
||||
var output = function(input){
|
||||
/* do something with the input */
|
||||
return input;
|
||||
}
|
||||
|
||||
this.output = output(input); // run the output and assign it to this.output
|
||||
options.step.output = output.src;
|
||||
callback();
|
||||
UI.onComplete(options.step); // tell UI we are done
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### options
|
||||
|
||||
The object `options` stores some important information. This is how you can accept
|
||||
input from users. If you require a variable "x" from the user and the user passes
|
||||
it in, you will be able to access it as `options.x`.
|
||||
|
||||
Options also has some in-built properties. The `options.inBrowser` boolean denotes
|
||||
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.
|
||||
|
||||
What is not in the draw method, but is in the `module.exports` is executed only
|
||||
when the step is added. So whatever external npm modules are to be loaded, or
|
||||
constant definitions must be done **outside** the `draw()` method's definition.
|
||||
|
||||
`draw()` receives two arguments - `input` and `callback` :
|
||||
* `input` is an object which is essentially the output of the previous step.
|
||||
```js
|
||||
input = {
|
||||
src: "<$DataURL>",
|
||||
format: "<png|jpeg|gif>"
|
||||
}
|
||||
```
|
||||
* `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
|
||||
|
||||
* `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:
|
||||
|
||||
* `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.
|
||||
* `UI.onComplete(options.step)` must be emitted whenever the output of a draw call
|
||||
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.
|
||||
|
||||
### Name and description
|
||||
|
||||
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",
|
||||
"description": "Optional longer text explanation of the module's function",
|
||||
"url": "Optional link to module's source code or documentation",
|
||||
"inputs": {
|
||||
"var1": {
|
||||
"type": "text",
|
||||
"default": "default value"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"out1": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Types may be one of "text", "integer", "float", "select".
|
||||
Integer and Float types should also specify minimum and maximum values like this:
|
||||
|
||||
```json
|
||||
"var1": {
|
||||
"type": "integer",
|
||||
"min": 0,
|
||||
"max": 4,
|
||||
"default": 1
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
The `green-channel` module is included into the core modules here: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
|
||||
|
||||
For help integrating, please open an issue.
|
||||
12
Gruntfile.js
@@ -1,6 +1,7 @@
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
|
||||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
|
||||
|
||||
@@ -16,6 +17,7 @@ module.exports = function(grunt) {
|
||||
files: [
|
||||
'src/*.js',
|
||||
'src/*/*.js',
|
||||
'src/*/*/*.js',
|
||||
'Gruntfile.js'
|
||||
],
|
||||
tasks: [ 'build:js' ]
|
||||
@@ -27,6 +29,13 @@ module.exports = function(grunt) {
|
||||
src: ['src/ImageSequencer.js'],
|
||||
dest: 'dist/image-sequencer.js'
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
dist: {
|
||||
src: ['./dist/image-sequencer.js'],
|
||||
dest: './dist/image-sequencer.min.js'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -35,7 +44,8 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('default', ['watch']);
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'browserify:dist'
|
||||
'browserify:dist',
|
||||
'uglify:dist'
|
||||
]);
|
||||
|
||||
};
|
||||
|
||||
376
README.md
@@ -1,17 +1,18 @@
|
||||
Image Sequencer
|
||||
====
|
||||
|
||||
aka "Consequencer"
|
||||
|
||||
[](https://travis-ci.org/publiclab/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,12 +20,57 @@ It is also for prototyping some other related ideas:
|
||||
* test-based image processing -- the ability to create a sequence of steps that do the same task as some other image processing tool, provable with example before/after images to compare with
|
||||
* logging of each step to produce an evidentiary record of modifications to an original image
|
||||
* cascading changes -- change an earlier step's settings, and see those changes affect later steps
|
||||
* "small modules"-based extensibility: see [Contributing](#contributing), below
|
||||
* "small modules"-based extensibility: see [Contributing](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Examples:
|
||||
## Examples
|
||||
|
||||
* [Basic example](https://jywarren.github.io/image-sequencer/)
|
||||
* [NDVI example](https://jywarren.github.io/image-sequencer/examples/ndvi/) - related to [Infragram.org](http://infragram.org)
|
||||
* [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:
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Quick Usage](#quick-usage)
|
||||
* [CLI Usage](#cli-usage)
|
||||
* [Classic Usage](#classic-usage)
|
||||
* [Method Chaining](#method-chaining)
|
||||
* [Multiple Images](#multiple-images)
|
||||
* [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. See the [demo here](https://publiclab.github.io/image-sequencer/)!
|
||||
|
||||
### Node (via NPM)
|
||||
|
||||
(You must have NPM for this)
|
||||
Add `image-sequencer` to your list of dependancies and run `$ npm install`
|
||||
|
||||
### CLI
|
||||
|
||||
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
|
||||
|
||||
@@ -49,6 +95,47 @@ For example:
|
||||
sequencer.replaceImage('#photo',['invert','ndvi-red']);
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
Image Sequencer also provides a CLI for applying operations to local files. The CLI takes the following arguments:
|
||||
|
||||
-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)
|
||||
-c | --config {object} | Options for the step. (optional)
|
||||
|
||||
The basic format for using the CLI is as follows:
|
||||
|
||||
```
|
||||
$ ./index.js -i [PATH] -s step-name
|
||||
```
|
||||
|
||||
*NOTE:* On Windows you'll have to use `node index.js` instead of `./index.js`.
|
||||
|
||||
The CLI also can take multiple steps at once, like so:
|
||||
|
||||
```
|
||||
$ ./index.js -i [PATH] -s "step-name-1 step-name-2 ..."
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -68,11 +155,26 @@ a name and an image. The method also accepts an optional callback.
|
||||
```js
|
||||
sequencer.loadImage(image_src,optional_callback);
|
||||
```
|
||||
On `Node.js` the `image_src` may be a DataURI or a local path. On browsers, it
|
||||
must be a DatURI (or 'selector to image' -- Work in Progress)
|
||||
On `Node.js` the `image_src` may be a DataURI or a local path or a URL.
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
On browsers, it may be a DatURI, a local image or a URL (Unless this violates
|
||||
CORS Restrictions). To sum up, these are accepted:
|
||||
* Images in the same domain (or directory - for a local implementation)
|
||||
* CORS-Proof images in another domain.
|
||||
* DataURLs
|
||||
|
||||
return value: **none** (A callback should be used to ensure the image gets loaded)
|
||||
The callback is called within the scope of a the sequencer. For example:
|
||||
(addSteps is defined later)
|
||||
|
||||
```js
|
||||
sequencer.loadImage('SRC',function(){
|
||||
this.addSteps('module-name');
|
||||
});
|
||||
```
|
||||
|
||||
The `this` refers to all the images added in the parent `loadImages` function only.
|
||||
In this case, only `'SRC'`.
|
||||
|
||||
### Adding steps to the image
|
||||
|
||||
@@ -156,6 +258,7 @@ return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
## Method Chaining
|
||||
Methods can be chained on the Image Sequencer:
|
||||
* loadImage()/loadImages() can only terminate a chain.
|
||||
* run() can not be in the middle of the chain.
|
||||
* If the chain starts with loadImage() or loadImages(), the following methods are
|
||||
applied only to the newly loaded images.
|
||||
@@ -164,9 +267,11 @@ be of the form "image<number>". For ex: "image1", "image2", "image3", etc.
|
||||
|
||||
Valid Chains:
|
||||
```js
|
||||
sequencer.loadImage('red').addSteps('invert').run(function(out){
|
||||
//do something with otuput.
|
||||
});
|
||||
sequencer.loadImage('red',function(){
|
||||
this.addSteps('invert').run(function(out){
|
||||
//do something with ouptut.
|
||||
});
|
||||
})
|
||||
sequencer.addSteps(['ndvi-red','invert']).run();
|
||||
et cetra.
|
||||
```
|
||||
@@ -209,7 +314,7 @@ with each image. This is a string literal.
|
||||
});
|
||||
```
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
return value: **none**
|
||||
|
||||
|
||||
### Adding Steps on Multiple Images
|
||||
@@ -325,204 +430,75 @@ sequencer.insertSteps({
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
|
||||
## Contributing
|
||||
## Creating a User Interface
|
||||
|
||||
Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`.
|
||||
Image Sequencer provides the following events which can be used to generate a UI:
|
||||
|
||||
### Contributing modules
|
||||
* `onSetup` : this event is triggered when a new module is set up. This can be used,
|
||||
for instance, to generate a DIV element to store the generated image for that step.
|
||||
* `onDraw` : This event is triggered when Image Sequencer starts drawing the output
|
||||
for a module. This can be used, for instance, to overlay a loading GIF over the DIV
|
||||
generated above.
|
||||
* `onComplete` : This event is triggered when Image Sequencer has drawn the output
|
||||
for a module. This can be used, for instance, to update the DIV with the new image
|
||||
and remove the loading GIF generated above.
|
||||
* `onRemove` : This event is triggered when a module is removed. This can be used,
|
||||
for instance, to remove the DIV generated above.
|
||||
|
||||
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
|
||||
|
||||
#### draw()
|
||||
|
||||
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
|
||||
|
||||
* `module.draw()`
|
||||
|
||||
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
|
||||
How to define these functions:
|
||||
|
||||
```js
|
||||
input = {
|
||||
src: "datauri here",
|
||||
format: "jpeg/png/etc"
|
||||
}
|
||||
sequencer.setUI({
|
||||
onSetup: function(step) {},
|
||||
onDraw: function(step) {},
|
||||
onComplete: function(step) {},
|
||||
onRemove: function(step) {}
|
||||
});
|
||||
```
|
||||
|
||||
The `image` object is essentially the output of the previous step.
|
||||
These methods can be defined and re-defined at any time, but it is advisable to
|
||||
set them before any module is added and not change it thereafter. This is because
|
||||
the `setUI` method will only affect the modules added after `setUI` is called.
|
||||
|
||||
The draw method must, when it is complete, pass the output image to the method `this.output = modified_input`, which will send the output to the next module in the chain. For example:
|
||||
The `onComplete` event is passed on the output of the module.
|
||||
|
||||
Image Sequencer provides a namespace `step` for the purpose of UI Creation in
|
||||
the scope of these definable function. This namespace has the following
|
||||
predefined properties:
|
||||
|
||||
* `step.name` : (String) Name of the step
|
||||
* `step.ID` : (Number) An ID given to every step of the sequencer, unique throughout.
|
||||
* `step.imageName` : (String) Name of the image the step is applied to.
|
||||
* `step.output` : (DataURL String) Output of the step.
|
||||
* `step.inBrowser` : (Boolean) Whether the client is a browser or not
|
||||
|
||||
In addition to these, one might define their own properties, which shall be
|
||||
accessible across all the event scopes of that step.
|
||||
|
||||
For example :
|
||||
|
||||
```js
|
||||
function draw(image) {
|
||||
|
||||
// do some stuff with the image
|
||||
|
||||
this.output = image;
|
||||
callback();
|
||||
}
|
||||
```
|
||||
|
||||
#### Title
|
||||
|
||||
For display in the web-based UI, each module may also have a title like `options.title`.
|
||||
|
||||
#### Module example
|
||||
|
||||
See existing module `green-channel` for an example: https://github.com/jywarren/image-sequencer/tree/master/src/modules/GreenChannel.js
|
||||
|
||||
For help integrating, please open an issue.
|
||||
|
||||
****
|
||||
|
||||
## Development
|
||||
|
||||
Notes on development next steps:
|
||||
|
||||
### UI
|
||||
|
||||
* [ ] add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib
|
||||
* [ ] it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
|
||||
|
||||
* [ ] spinners before panels are complete
|
||||
* [ ] is there a module for generating forms from parameters?
|
||||
* [ ] click to expand for all images
|
||||
* [ ] `ImageSequencer.Renderer` class to manage image output formats and adapters
|
||||
* [ ] remove step
|
||||
|
||||
* [ ] output besides an image -- like `message(txt)` to display to the step's UI
|
||||
|
||||
|
||||
### Modularization
|
||||
|
||||
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
|
||||
* [x] ability to start running at any point -- already works?
|
||||
* [x] commandline runnability?
|
||||
* [x] Make available as browserified OR `require()` includable...
|
||||
* [ ] standardize panel addition with submodule that offers Panel.display(image)
|
||||
* [ ] allow passing data as data-uri or Image object, or stream, or ndarray or ImageData array, if both of neighboring pair has ability?
|
||||
* see https://github.com/jywarren/image-sequencer/issues/1
|
||||
* [ ] ...could we directly include package.json for module descriptions? At least as a fallback.
|
||||
* [ ] (for node-and-line style UIs) non-linear sequences with Y-splitters
|
||||
* [ ] `sequencer.addModule('path/to/module.js')` style module addition -- also to avoid browserifying all of Plotly :-P
|
||||
* [x] remove step
|
||||
|
||||
### Testing
|
||||
|
||||
* [ ] tests - modules headless; unit tests
|
||||
* [ ] comparisons with diff
|
||||
* [ ] testing a module's promised functionality: each module could offer before/after images as part of their API; by running the module on the before image, you should get exactly the after image, comparing with an image diff
|
||||
|
||||
### Use cases
|
||||
|
||||
* [ ] make an Infragram module that accepts a math expression
|
||||
|
||||
### Bugs
|
||||
|
||||
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
|
||||
* we should make defaults a config of the first module
|
||||
|
||||
****
|
||||
|
||||
## Module Candidates
|
||||
|
||||
* https://github.com/linuxenko/rextract.js
|
||||
* https://www.npmjs.com/package/histogram
|
||||
* https://github.com/hughsk/flood-fill
|
||||
* https://www.npmjs.com/package/blink-diff
|
||||
* smaller and faster: https://www.npmjs.com/package/@schornio/pixelmatch
|
||||
* https://github.com/yahoo/pngjs-image has lots of useful general-purpose image getters like `image.getLuminosityAtIndex(idx)`
|
||||
* some way to add in a new image (respecting alpha) -- `add-image` (with blend mode, default `normal`?)
|
||||
* https://github.com/yuta1984/CannyJS - edge detection
|
||||
* http://codepen.io/taylorcoffelt/pen/EsCcr - more edge detection
|
||||
|
||||
## Ideas
|
||||
|
||||
* https://github.com/vicapow/jsqrcode
|
||||
* https://github.com/jadnco/whirl - scrubbable image sequence player
|
||||
* non graphics card GL functions could be shimmed with https://github.com/Overv/JSGL
|
||||
* or this: https://github.com/stackgl/headless-gl
|
||||
* https://github.com/mattdesl/fontpath-simple-renderer
|
||||
* output in animated Gif? as a module
|
||||
|
||||
### Referencing earlier states
|
||||
|
||||
Complex sequences with masking could require accessing previous states (or nonlinearity):
|
||||
|
||||
* flood-fill an area
|
||||
* select only the flooded area
|
||||
* roundabout: lighten everything to <50%, then flood-fill with black? Not 100% reliable.
|
||||
* roundabout 2: `flood fill`, then `blink-diff` with original
|
||||
* then add step which recovers original image, repeat `flood-fill`/`blink-diff` for second region
|
||||
* reference above masked states in a `mask` module, with `maskModule.draw(image, { getMask: function() { return maskImg } })`
|
||||
|
||||
****
|
||||
|
||||
**Notes:**
|
||||
|
||||
`pattern-fill` module to use patterns in JS canvas:
|
||||
|
||||
```js
|
||||
var c=document.getElementById("myCanvas");
|
||||
var ctx=c.getContext("2d");
|
||||
var img=document.getElementById("lamp");
|
||||
var pat=ctx.createPattern(img,"repeat");
|
||||
ctx.rect(0,0,150,100);
|
||||
ctx.fillStyle=pat;
|
||||
ctx.fill();
|
||||
```
|
||||
|
||||
Masking:
|
||||
|
||||
```js
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(160, 600);
|
||||
ctx.rect(0, 0, 160, 600);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(img, 0, 0);
|
||||
ctx.restore();
|
||||
```
|
||||
|
||||
****
|
||||
|
||||
## UI notes:
|
||||
|
||||
* visual nodes-and-lines UI: https://github.com/flowhub/the-graph
|
||||
* https://flowhub.github.io/the-graph/examples/demo-simple.html
|
||||
|
||||
|
||||
|
||||
```js
|
||||
|
||||
settings: {
|
||||
'threshold': {
|
||||
type: 'slider',
|
||||
label: 'Threshold',
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100
|
||||
sequencer.setUI({
|
||||
onSetup: function(step){
|
||||
// Create new property "step.image"
|
||||
step.image = document.createElement('img');
|
||||
document.body.append(step.image);
|
||||
},
|
||||
'colors': {
|
||||
type: 'select',
|
||||
label: 'Colors',
|
||||
options: [
|
||||
{ name: '0', value: '0', default: true },
|
||||
{ name: '1', value: '1' },
|
||||
{ name: '2', value: '2' }
|
||||
]
|
||||
onComplete: function(step){
|
||||
// Access predefined "step.output" and user-defined "step.image"
|
||||
step.image.src = step.output;
|
||||
},
|
||||
onRemove: function(step){
|
||||
// Access user-defined "step.image"
|
||||
step.image.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Possible web-based commandline interface: https://hyper.is/?
|
||||
Note: `identity.imageName` is the "name" of that particular image. This name can
|
||||
be specified while loading the image via `sequencer.loadImage("name","SRC")`. If
|
||||
not specified, the name of a loaded image defaults to a name like "image1",
|
||||
"image2", et cetra.
|
||||
|
||||
|
||||
### Path cutting
|
||||
|
||||
* threshold
|
||||
* vectorize
|
||||
* edge detect
|
||||
* direction find (vectorize and colorize)
|
||||
Details of all modules can be sought using `sequencer.modulesInfo()`.
|
||||
This method returns an object which defines the name and inputs of the modules. If a module name (hyphenated) is passed in the method, then only the details of that module are returned.
|
||||
|
||||
36696
dist/image-sequencer.js
vendored
Normal file → Executable file
1
dist/image-sequencer.min.js
vendored
Normal file
@@ -47,3 +47,24 @@ where `options` is an object with the property `colormap`. `options.colormap` ca
|
||||
* "fastie" : [[0, [255, 255, 255], [0, 0, 0]], [0.167, [0, 0, 0], [255, 255, 255]], [0.33, [255, 255, 255], [0, 0, 0]], [0.5, [0, 0, 0], [140, 140, 255]], [0.55, [140, 140, 255], [0, 255, 0]], [0.63, [0, 255, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 0, 0]], [0.95, [255, 0, 0], [255, 0, 255]]]
|
||||
|
||||
* A custom array.
|
||||
|
||||
## FisheyeGl (fisheye-gl)
|
||||
|
||||
This module is used for correcting Fisheye or Lens Distortion
|
||||
|
||||
#### Usage
|
||||
|
||||
```js
|
||||
sequencer.loadImage('PATH')
|
||||
.addSteps('fisheye-gl',options)
|
||||
.run()
|
||||
```
|
||||
|
||||
where `options` is an object with the following properties:
|
||||
* a : a correction (0 to 4; default 1)
|
||||
* b : b correction (0 to 4; default 1)
|
||||
* Fx : x correction (0 to 4; default 1)
|
||||
* Fy : y correction (0 to 4; default 1)
|
||||
* scale : The ratio to which the original image is to be scaled (0 to 20; default 1.5)
|
||||
* x : Field of View x (0 to 2; default 1)
|
||||
* y : Field of View y (0 to 2; default 1)
|
||||
|
||||
109
examples/demo.css
Normal file
@@ -0,0 +1,109 @@
|
||||
/* https://github.com/theleagueof/league-spartan */
|
||||
@font-face {
|
||||
font-family: 'League Spartan';
|
||||
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot');
|
||||
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff2') format('woff2'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff') format('woff'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.ttf') format('truetype'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.svg#league_spartanbold') format('svg');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 20px;
|
||||
margin: 0 auto 200px;
|
||||
max-width: 1000px;
|
||||
background: #f8f8fa;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'League Spartan';
|
||||
color: #445;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#dropzone {
|
||||
padding: 30px;
|
||||
margin: 0 20% 30px;
|
||||
border: 4px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#dropzone input {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.step .img-thumbnail {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.details {
|
||||
border-top: 3px solid #444;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.details h3 {
|
||||
font-family: monospace;
|
||||
margin-top: 12px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.det {
|
||||
padding: 10px 16px;
|
||||
text-decoration: italic;
|
||||
}
|
||||
|
||||
.tools {
|
||||
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;
|
||||
}
|
||||
|
||||
#addStep .labels {
|
||||
text-align: right;
|
||||
padding: 6px;
|
||||
margin: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width:768px) {
|
||||
#addStep .labels {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
#addStep .add {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
}
|
||||
28
examples/demo.js
Normal file
@@ -0,0 +1,28 @@
|
||||
window.onload = function() {
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
// Load information of all modules (Name, Inputs, Outputs)
|
||||
var modulesInfo = sequencer.modulesInfo();
|
||||
|
||||
// Add modules to the addStep dropdown
|
||||
for (var m in modulesInfo) {
|
||||
$("#addStep select").append(
|
||||
'<option value="' + m + '">' + modulesInfo[m].name + "</option>"
|
||||
);
|
||||
}
|
||||
|
||||
// UI for each step:
|
||||
sequencer.setUI(DefaultHtmlStepUi(sequencer));
|
||||
|
||||
// UI for the overall demo:
|
||||
var ui = DefaultHtmlSequencerUi(sequencer);
|
||||
|
||||
sequencer.loadImage("images/tulips.png", ui.onLoad);
|
||||
|
||||
$("#addStep select").on("change", ui.selectNewStepUi);
|
||||
$("#addStep button").on("click", ui.addStepUi);
|
||||
$('body').on('click', 'button.remove', ui.removeStepUi);
|
||||
|
||||
// image selection and drag/drop handling from examples/lib/imageSelection.js
|
||||
setupFileHandling(sequencer);
|
||||
};
|
||||
137
examples/fisheye.html
Normal file
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<title>Fisheye Removal | Image Sequencer</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<style media="screen">
|
||||
.r.18 {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
.m {
|
||||
margin: 0 5px;
|
||||
}
|
||||
div.c.m {
|
||||
width: 100px;
|
||||
}
|
||||
.r span {
|
||||
width: 30px !important;
|
||||
}
|
||||
@media all and (max-width: 1000px) {
|
||||
div#main {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../dist/image-sequencer.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h2>Image Sequencer</h2>
|
||||
<h5>FisheyeGl Demo</h5>
|
||||
</header>
|
||||
|
||||
<section class="rh">
|
||||
<div class="r">
|
||||
Drag the Slider to adjust values
|
||||
</div>
|
||||
<div class="r">
|
||||
Select an Image <input type="file" id="file" value="">
|
||||
</div>
|
||||
<div class="r" id="main">
|
||||
<div class="c">
|
||||
<img id="fisheye" src="" alt="">
|
||||
</div>
|
||||
<div class="c rh">
|
||||
<div class="r 18">
|
||||
<div class="c m 30">Fx:</div>
|
||||
<input class="c" name="Fx" type="range" min="0" max="2" step="0.01" value="0" />
|
||||
<span>0</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">Fy:</div>
|
||||
<input class="c" name="Fy" type="range" min="0" max="2" step="0.01" value="0" />
|
||||
<span>0</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">a:</div>
|
||||
<input class="c" name="a" type="range" min="0" max="4" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">b:</div>
|
||||
<input class="c" name="b" type="range" min="0" max="4" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">scale:</div>
|
||||
<input class="c" name="scale" type="range" min="0" max="20" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">FOV x:</div>
|
||||
<input class="c" name="x" type="range" min="0" max="2" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">FOV y:</div>
|
||||
<input class="c" name="y" type="range" min="0" max="2" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer> <a href="https://github.com/publiclab/image-sequencer">View on GitHub</a> </footer>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
sequencer = ImageSequencer();
|
||||
var image = document.querySelector('#fisheye');
|
||||
|
||||
sequencer.setUI({
|
||||
onComplete: function(step){
|
||||
image.src = step.output;
|
||||
}
|
||||
});
|
||||
|
||||
sequencer.loadImage('fisheye','images/grid.png',function(){
|
||||
this.addSteps('fisheye-gl').run();
|
||||
});
|
||||
|
||||
var inputs = document.querySelectorAll('input[type="range"]')
|
||||
for(i in inputs)
|
||||
inputs[i].oninput = function(e) {
|
||||
e.target.nextSibling.nextSibling.innerHTML = e.target.value;
|
||||
sequencer.images.fisheye.steps[1].options[e.target.name] = e.target.value;
|
||||
sequencer.run(1);
|
||||
}
|
||||
|
||||
document.querySelector('#file').onchange = function(e) {
|
||||
var file = e.target.files[0];
|
||||
|
||||
if(!file) return;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
sequencer.images.fisheye.steps[0].output.src = reader.result;
|
||||
sequencer.run(0);
|
||||
}
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
examples/images/IS-QR.js
Normal file
BIN
examples/images/IS-QR.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
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/grid-crop.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
BIN
examples/images/load.gif
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
examples/images/monarch.png
Normal file
|
After Width: | Height: | Size: 600 KiB |
|
Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 288 B |
BIN
examples/images/red.png
Normal file
|
After Width: | Height: | Size: 277 B |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
BIN
examples/images/test.gif
Normal file
|
After Width: | Height: | Size: 12 KiB |
1
examples/images/test.gif.js
Normal file
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
1
examples/images/test.png.js
Normal file
BIN
examples/images/tulips.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
95
examples/index.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
<link rel="icon" sizes="192x192" href="../icons/ic_192.png">
|
||||
|
||||
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
|
||||
<script src="lib/urlHash.js" charset="utf-8"></script>
|
||||
<script src="lib/imageSelection.js" charset="utf-8"></script>
|
||||
<script src="lib/defaultHtmlStepUi.js" charset="utf-8"></script>
|
||||
<script src="lib/defaultHtmlSequencerUi.js" charset="utf-8"></script>
|
||||
<script src="demo.js" charset="utf-8"></script>
|
||||
<!-- for crop module: -->
|
||||
<script src="../node_modules/imgareaselect/jquery.imgareaselect.dev.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<!-- for crop module: -->
|
||||
<link href="../node_modules/imgareaselect/distfiles/css/imgareaselect-default.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<header class="text-center">
|
||||
<h1>Image Sequencer</h1>
|
||||
<p>
|
||||
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original image, it
|
||||
creates a new image at each step in a sequence.
|
||||
<a href="https://publiclab.org/image-sequencer">Learn more</a>
|
||||
</p>
|
||||
<p>
|
||||
Open Source
|
||||
<a href="https://github.com/publiclab/image-sequencer">
|
||||
<i class="fa fa-github"></i>
|
||||
</a> by
|
||||
<a href="https://publiclab.org">Public Lab</a>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div id="dropzone">
|
||||
<p>
|
||||
<i>Select or drag in an image to start!</i>
|
||||
</p>
|
||||
<center>
|
||||
<input type="file" id="fileInput" value="">
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<section id="steps" class="row"></section>
|
||||
|
||||
<hr />
|
||||
|
||||
<section id="addStep" class="panel panel-primary">
|
||||
<div class="form-inline">
|
||||
<div class="panel-body">
|
||||
<div style="text-align:center;">
|
||||
<select class="form-control input-lg" id="selectStep">
|
||||
<option value="none" disabled selected>Select a new step...</option>
|
||||
</select>
|
||||
<button class="btn btn-success btn-lg" name="add">Add Step</button>
|
||||
</div>
|
||||
<br />
|
||||
<p class="info" style="padding:8px;">Select a new module to add to your sequence.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var sequencer;
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
60
examples/lib/defaultHtmlSequencerUi.js
Normal file
@@ -0,0 +1,60 @@
|
||||
function DefaultHtmlSequencerUi(_sequencer, options) {
|
||||
|
||||
options = options || {};
|
||||
var addStepSel = options.addStepSel = options.addStepSel || "#addStep";
|
||||
var removeStepSel = options.removeStepSel = options.removeStepSel || "button.remove";
|
||||
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
|
||||
|
||||
function onLoad() {
|
||||
importStepsFromUrlHash();
|
||||
}
|
||||
|
||||
// look up needed steps from Url Hash:
|
||||
function importStepsFromUrlHash() {
|
||||
var hash = getUrlHashParameter("steps");
|
||||
|
||||
if (hash) {
|
||||
var stepsFromHash = hash.split(",");
|
||||
stepsFromHash.forEach(function eachStep(step) {
|
||||
_sequencer.addSteps(step);
|
||||
});
|
||||
_sequencer.run();
|
||||
}
|
||||
}
|
||||
|
||||
function selectNewStepUi() {
|
||||
var m = $(addStepSel + " select").val();
|
||||
$(addStepSel + " .info").html(_sequencer.modulesInfo(m).description);
|
||||
}
|
||||
|
||||
function removeStepUi() {
|
||||
var index = $(removeStepSel).index(this) + 1;
|
||||
sequencer.removeSteps(index).run();
|
||||
// remove from URL hash too
|
||||
var urlHash = getUrlHashParameter("steps").split(",");
|
||||
urlHash.splice(index - 1, 1);
|
||||
setUrlHashParameter("steps", urlHash.join(","));
|
||||
}
|
||||
|
||||
function addStepUi() {
|
||||
if ($(addStepSel + " select").val() == "none") return;
|
||||
|
||||
// add to URL hash too
|
||||
var hash = getUrlHashParameter("steps") || "";
|
||||
if (hash != "") hash += ",";
|
||||
setUrlHashParameter("steps", hash + $(addStepSel + " select").val());
|
||||
|
||||
var newStepName = $(addStepSel + " select").val();
|
||||
_sequencer
|
||||
.addSteps(newStepName, options)
|
||||
.run(null);
|
||||
}
|
||||
|
||||
return {
|
||||
onLoad: onLoad,
|
||||
importStepsFromUrlHash: importStepsFromUrlHash,
|
||||
selectNewStepUi: selectNewStepUi,
|
||||
removeStepUi: removeStepUi,
|
||||
addStepUi: addStepUi
|
||||
}
|
||||
}
|
||||
184
examples/lib/defaultHtmlStepUi.js
Normal file
@@ -0,0 +1,184 @@
|
||||
// Set the UI in sequencer. This Will generate HTML based on
|
||||
// Image Sequencer events :
|
||||
// onSetup : Called every time a step is added
|
||||
// onDraw : Called every time a step starts draw
|
||||
// onComplete : Called every time a step finishes drawing
|
||||
// onRemove : Called everytime a step is removed
|
||||
// The variable 'step' stores useful data like input and
|
||||
// output values, step information.
|
||||
// See documetation for more details.
|
||||
function DefaultHtmlStepUi(_sequencer, options) {
|
||||
|
||||
options = options || {};
|
||||
var stepsEl = options.stepsEl || document.querySelector("#steps");
|
||||
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
|
||||
|
||||
function onSetup(step) {
|
||||
if (step.options && step.options.description)
|
||||
step.description = step.options.description;
|
||||
|
||||
step.ui =
|
||||
'\
|
||||
<div class="row step">\
|
||||
<div class="col-md-4 details">\
|
||||
<h3>' +
|
||||
step.name +
|
||||
"</h3>\
|
||||
<p><i>" +
|
||||
(step.description || "") +
|
||||
'</i></p>\
|
||||
</div>\
|
||||
<div class="col-md-8">\
|
||||
<div class="load" style="display:none;"><i class="fa fa-circle-o-notch fa-spin"></i></div>\
|
||||
<a><img alt="" style="max-width=100%" class="img-thumbnail" /></a>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn btn-default">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
</div>';
|
||||
|
||||
var parser = new DOMParser();
|
||||
step.ui = parser.parseFromString(step.ui, "text/html");
|
||||
step.ui = step.ui.querySelector("div.row");
|
||||
step.linkElement = step.ui.querySelector("a");
|
||||
step.imgElement = step.ui.querySelector("a img");
|
||||
|
||||
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = _sequencer.modulesInfo(step.name).outputs;
|
||||
var merged = Object.assign(inputs, outputs); // combine outputs w inputs
|
||||
|
||||
for (var paramName in merged) {
|
||||
var isInput = inputs.hasOwnProperty(paramName);
|
||||
var html = "";
|
||||
var inputDesc = isInput ? inputs[paramName] : {};
|
||||
if (!isInput) {
|
||||
html += '<span class="output"></span>';
|
||||
} else if (inputDesc.type.toLowerCase() == "select") {
|
||||
html += '<select class="form-control" name="' + paramName + '">';
|
||||
for (var option in inputDesc.values) {
|
||||
html += "<option>" + inputDesc.values[option] + "</option>";
|
||||
}
|
||||
html += "</select>";
|
||||
} else {
|
||||
html =
|
||||
'<input class="form-control" type="' +
|
||||
inputDesc.type +
|
||||
'" name="' +
|
||||
paramName +
|
||||
'">';
|
||||
}
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.className = "row";
|
||||
div.setAttribute("name", paramName);
|
||||
var description = inputs[paramName].desc || paramName;
|
||||
div.innerHTML =
|
||||
"<div class='det'>\
|
||||
<label for='" +
|
||||
paramName +
|
||||
"'>" +
|
||||
description +
|
||||
"</label>\
|
||||
" +
|
||||
html +
|
||||
"\
|
||||
</div>";
|
||||
step.ui.querySelector("div.details").appendChild(div);
|
||||
}
|
||||
$(step.ui.querySelector("div.details")).append(
|
||||
"<p><button class='btn btn-default btn-save'>Save</button></p>"
|
||||
);
|
||||
|
||||
function saveOptions() {
|
||||
$(step.ui.querySelector("div.details"))
|
||||
.find("input,select")
|
||||
.each(function(i, input) {
|
||||
step.options[$(input).attr("name")] = input.value;
|
||||
});
|
||||
_sequencer.run();
|
||||
}
|
||||
|
||||
saveOptions();
|
||||
|
||||
// on clicking Save in the details pane of the step
|
||||
$(step.ui.querySelector("div.details .btn-save")).click(saveOptions);
|
||||
}
|
||||
|
||||
if (step.name != "load-image")
|
||||
step.ui
|
||||
.querySelector("div.details")
|
||||
.appendChild(
|
||||
parser.parseFromString(tools, "text/html").querySelector("div")
|
||||
);
|
||||
|
||||
stepsEl.appendChild(step.ui);
|
||||
}
|
||||
|
||||
function onDraw(step) {
|
||||
$(step.ui.querySelector(".load")).show();
|
||||
$(step.ui.querySelector("img")).hide();
|
||||
}
|
||||
|
||||
function onComplete(step) {
|
||||
$(step.ui.querySelector(".load")).hide();
|
||||
$(step.ui.querySelector("img")).show();
|
||||
|
||||
step.imgElement.src = step.output;
|
||||
step.linkElement.href = step.output;
|
||||
|
||||
// TODO: use a generalized version of this
|
||||
function fileExtension(output) {
|
||||
return output.split("/")[1].split(";")[0];
|
||||
}
|
||||
|
||||
step.linkElement.download = step.name + "." + fileExtension(step.output);
|
||||
step.linkElement.target = "_blank";
|
||||
|
||||
// fill inputs with stored step options
|
||||
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = _sequencer.modulesInfo(step.name).outputs;
|
||||
for (var i in inputs) {
|
||||
if (
|
||||
step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "input"
|
||||
)
|
||||
step.ui.querySelector('div[name="' + i + '"] input').value =
|
||||
step.options[i];
|
||||
if (
|
||||
step.options[i] !== undefined &&
|
||||
inputs[i].type.toLowerCase() === "select"
|
||||
)
|
||||
step.ui.querySelector('div[name="' + i + '"] select').value =
|
||||
step.options[i];
|
||||
}
|
||||
for (var i in outputs) {
|
||||
if (step[i] !== undefined)
|
||||
step.ui.querySelector('div[name="' + i + '"] input').value =
|
||||
step[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRemove(step) {
|
||||
step.ui.remove();
|
||||
}
|
||||
|
||||
function getPreview() {
|
||||
return step.imgElement;
|
||||
}
|
||||
|
||||
return {
|
||||
getPreview: getPreview,
|
||||
onSetup: onSetup,
|
||||
onComplete: onComplete,
|
||||
onRemove: onRemove,
|
||||
onDraw: onDraw
|
||||
}
|
||||
}
|
||||
50
examples/lib/imageSelection.js
Normal file
@@ -0,0 +1,50 @@
|
||||
function setupFileHandling(_sequencer, dropzoneId, fileInputId) {
|
||||
|
||||
dropzoneId = dropzoneId || "dropzone";
|
||||
var dropzone = $('#' + dropzoneId);
|
||||
|
||||
fileInputId = fileInputId || "fileInput";
|
||||
var fileInput = $('#' + fileInputId);
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
function handleFile(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // stops the browser from redirecting.
|
||||
|
||||
if (e.target && e.target.files) var file = e.target.files[0];
|
||||
else var file = e.dataTransfer.files[0];
|
||||
if(!file) return;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function onFileReaderLoad() {
|
||||
var loadStep = _sequencer.images.image1.steps[0];
|
||||
loadStep.output.src = reader.result;
|
||||
_sequencer.run(0);
|
||||
loadStep.options.step.imgElement.src = reader.result;
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
fileInput.on('change', handleFile);
|
||||
|
||||
dropzone[0].addEventListener('drop', handleFile, false);
|
||||
|
||||
dropzone.on('dragover', function onDragover(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
}, false);
|
||||
|
||||
dropzone.on('dragenter', function onDragEnter(e) {
|
||||
dropzone.addClass('hover');
|
||||
});
|
||||
|
||||
dropzone.on('dragleave', function onDragLeave(e) {
|
||||
dropzone.removeClass('hover');
|
||||
});
|
||||
|
||||
}
|
||||
42
examples/lib/urlHash.js
Normal file
@@ -0,0 +1,42 @@
|
||||
function getUrlHashParameter(param) {
|
||||
|
||||
var params = getUrlHashParameters();
|
||||
return params[param];
|
||||
|
||||
}
|
||||
|
||||
function getUrlHashParameters() {
|
||||
|
||||
var sPageURL = window.location.hash;
|
||||
if (sPageURL) sPageURL = sPageURL.split('#')[1];
|
||||
var pairs = sPageURL.split('&');
|
||||
var object = {};
|
||||
pairs.forEach(function(pair, i) {
|
||||
pair = pair.split('=');
|
||||
if (pair[0] != '') object[pair[0]] = pair[1];
|
||||
});
|
||||
return object;
|
||||
}
|
||||
|
||||
// accepts an object like { paramName: value, paramName1: value }
|
||||
// and transforms to: url.com#paramName=value¶mName1=value
|
||||
function setUrlHashParameters(params) {
|
||||
|
||||
var keys = Object.keys(params);
|
||||
var values = Object.values(params);
|
||||
var pairs = [];
|
||||
keys.forEach(function(key, i) {
|
||||
if (key != '') pairs.push(keys[i] + '=' + values[i]);
|
||||
});
|
||||
var hash = pairs.join('&');
|
||||
window.location.hash = hash;
|
||||
|
||||
}
|
||||
|
||||
function setUrlHashParameter(param, value) {
|
||||
|
||||
var params = getUrlHashParameters();
|
||||
params[param] = value;
|
||||
setUrlHashParameters(params);
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="../dist/image-sequencer.css" rel="stylesheet">
|
||||
<link href="demo-old.css" rel="stylesheet">
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="panels">
|
||||
|
||||
<div class="panel ismod-image-select" style="display:flex;justify-content:center">
|
||||
<img src="replace.jpg" id="pencils" style="cursor:pointer">
|
||||
<img src="images/replace.jpg" id="pencils" style="cursor:pointer">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
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 |
82
index.html
@@ -1,87 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<link href="node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="dist/image-sequencer.css" rel="stylesheet">
|
||||
|
||||
<script src="node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="dist/image-sequencer.js"></script>
|
||||
|
||||
<meta http-equiv="refresh" content="0; url=examples/" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
|
||||
<h1>Image Sequencer</h1>
|
||||
<h3>
|
||||
<a href="https://github.com/jywarren/image-sequencer"><i class="fa fa-github"></i></a>
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
|
||||
<p style="display:none;" class="spinner"><i class="fa fa-spinner fa-spin"></i></p>
|
||||
|
||||
<div class="panels">
|
||||
|
||||
<div class="panel ismod-image-select">
|
||||
<div class="mod-drop">Drag image here</div>
|
||||
<p class="instructions">Select or drop an image here to begin.</p>
|
||||
<input id="file-select" type="file" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mod-new-panel">
|
||||
<form class="mod-new-panel">
|
||||
<p class="instructions">Add a new step</p>
|
||||
<select class="select-module form-control" style="margin-bottom:6px;">
|
||||
<option value="ndvi-red">NDVI with red filter</option>
|
||||
<option value="green-channel">Green channel</option>
|
||||
<option value="plot">Plot with colorbar</option>
|
||||
<option value="image-threshold">Threshold image</option>
|
||||
</select>
|
||||
<p><button class="btn btn-default add-step">Add step</button></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="log">
|
||||
<h4>Log</h4>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
var sequencer;
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
sequencer.loadImage('examples/grid.png');
|
||||
|
||||
sequencer.addStep('ndvi-red');
|
||||
sequencer.addStep('image-threshold');
|
||||
sequencer.addStep('crop');
|
||||
//sequencer.addStep('plot');
|
||||
|
||||
$('.add-step').click(function(e) {
|
||||
e.preventDefault();
|
||||
sequencer.addStep($('.select-module').val());
|
||||
sequencer.run(sequencer.options.initialImage); // later we might only run this step, if we can fetch the image output from the previous
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
172
index.js
Normal file → Executable file
@@ -1,11 +1,165 @@
|
||||
console.log('\x1b[31m%s\x1b[0m',"This is the output of the module");
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('./src/ImageSequencer');
|
||||
sequencer = ImageSequencer();
|
||||
sequencer.loadImages({images:{red:'examples/red.jpg'},callback:function(){
|
||||
sequencer.addSteps(['do-nothing-pix','ndvi-red','invert']);
|
||||
sequencer.removeSteps(1);
|
||||
sequencer.insertSteps({
|
||||
red: [{index: -1, name: 'do-nothing-pix', o:{}}]
|
||||
sequencer = ImageSequencer({ui: false});
|
||||
var Spinner = require('ora')
|
||||
|
||||
var program = require('commander');
|
||||
var readlineSync = require('readline-sync');
|
||||
|
||||
function exit(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
program
|
||||
.version('0.1.0')
|
||||
.option('-i, --image [PATH/URL]', 'Input image URL')
|
||||
.option('-s, --step [step-name]', 'Name of the step to be added.')
|
||||
.option('-o, --output [PATH]', 'Directory where output will be stored.')
|
||||
.option('-b, --basic','Basic mode outputs only final image')
|
||||
.option('-c, --config [Object]', 'Options for the step')
|
||||
.parse(process.argv);
|
||||
|
||||
// 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.
|
||||
if(!program.image) exit("Can't read file.")
|
||||
|
||||
// User must input an image.
|
||||
require('fs').access(program.image, function(err){
|
||||
if(err) exit("Can't read file.")
|
||||
});
|
||||
|
||||
// User must input a step. If steps exist, check that every step is a valid step.
|
||||
if(!program.step || !validateSteps(program.step))
|
||||
exit("Please ensure all steps are valid.");
|
||||
|
||||
// If 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(){
|
||||
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(){
|
||||
|
||||
// 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!!`)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
sequencer.run();
|
||||
}});
|
||||
|
||||
});
|
||||
|
||||
// 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) {
|
||||
// If any step in the array is not valid (not a property of modulesInfo), set valid to false.
|
||||
if (!sequencer.modulesInfo().hasOwnProperty(step)) {
|
||||
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;
|
||||
}
|
||||
38
package.json
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "image-sequencer",
|
||||
"version": "0.0.1",
|
||||
"version": "1.5.0",
|
||||
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
|
||||
"main": "dist/image-sequencer.js",
|
||||
"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",
|
||||
@@ -21,27 +22,42 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "~3.2.0",
|
||||
"buffer": "~5.0.2",
|
||||
"commander": "^2.11.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"fisheyegl": "^0.1.2",
|
||||
"font-awesome": "~4.5.0",
|
||||
"get-pixels": "~3.3.0",
|
||||
"jquery": "~2",
|
||||
"urify": "^2.1.0"
|
||||
"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",
|
||||
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"base64-stream": "~0.1.3",
|
||||
"browserify": "13.0.0",
|
||||
"buffer": "~5.0.2",
|
||||
"get-pixels": "~3.3.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-uglify-es": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"image-filter-core": "~1.0.0",
|
||||
"image-filter-threshold": "~1.0.0",
|
||||
"looks-same": "^3.2.1",
|
||||
"matchdep": "^0.3.0",
|
||||
"plotly.js": "~1.21.2",
|
||||
"save-pixels": "~2.3.4",
|
||||
"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"
|
||||
"homepage": "https://github.com/publiclab/image-sequencer",
|
||||
"bin": {
|
||||
"sequencer": "./index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
function AddStep(ref, image, name, o) {
|
||||
// add steps to the sequencer
|
||||
function AddStep(_sequencer, image, name, o) {
|
||||
|
||||
function addStep(image, name, o_) {
|
||||
ref.log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
|
||||
var moduleInfo = _sequencer.modules[name][1];
|
||||
|
||||
var o = ref.copy(o_);
|
||||
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o_.name || name;
|
||||
var o = _sequencer.copy(o_);
|
||||
o.number = _sequencer.options.sequencerCounter++; // gives a unique ID to each step
|
||||
o.name = o_.name || name || moduleInfo.name;
|
||||
o.description = o_.description || moduleInfo.description;
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.container = o_.container || _sequencer.options.selector;
|
||||
o.image = image;
|
||||
o.inBrowser = _sequencer.options.inBrowser;
|
||||
|
||||
var module = ref.modules[name].call(ref.images,o);
|
||||
ref.images[image].steps.push(module);
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: _sequencer.options.inBrowser,
|
||||
ui: _sequencer.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = _sequencer.events;
|
||||
var module = _sequencer.modules[name][0](o,UI);
|
||||
_sequencer.images[image].steps.push(module);
|
||||
|
||||
|
||||
function defaultSetupModule() {
|
||||
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
|
||||
selector: o.selector,
|
||||
title: module.options.title,
|
||||
id: o.id
|
||||
});
|
||||
}
|
||||
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
|
||||
else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?)
|
||||
|
||||
// tell the UI that a step has been added.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
60
src/ExportBin.js
Executable file
@@ -0,0 +1,60 @@
|
||||
var fs = require('fs');
|
||||
var getDirectories = function(rootDir, cb) {
|
||||
fs.readdir(rootDir, function(err, files) {
|
||||
var dirs = [];
|
||||
if(typeof(files)=="undefined" || files.length == 0) {
|
||||
cb(dirs);
|
||||
return [];
|
||||
}
|
||||
for (var index = 0; index < files.length; ++index) {
|
||||
var file = files[index];
|
||||
if (file[0] !== '.') {
|
||||
var filePath = rootDir + '/' + file;
|
||||
fs.stat(filePath, function(err, stat) {
|
||||
if (stat.isDirectory()) {
|
||||
dirs.push(this.file);
|
||||
}
|
||||
if (files.length === (this.index + 1)) {
|
||||
return cb(dirs);
|
||||
}
|
||||
}.bind({index: index, file: file}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) console.error(err)
|
||||
});
|
||||
getDirectories(dir,function(dirs){
|
||||
var num = 1;
|
||||
for(var d in dirs){
|
||||
if(dirs[d].match(/^sequencer(.*)$/)==null) continue;
|
||||
var n = parseInt(dirs[d].match(/^sequencer(.*)$/)[1]);
|
||||
num = (n>=num)?(n+1):num;
|
||||
}
|
||||
fs.mkdir(dir+'sequencer'+num,function(){
|
||||
var root = dir+'sequencer'+num+'/';
|
||||
for(var image in ref.images) {
|
||||
var steps = ref.images[image].steps;
|
||||
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(){});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
if (typeof window !== 'undefined') {window.$ = window.jQuery = require('jquery'); isBrowser = true}
|
||||
if (typeof window !== 'undefined') {isBrowser = true}
|
||||
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,135 +30,182 @@ 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 = [];
|
||||
|
||||
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(){
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
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) {
|
||||
log('\x1b[31m%s\x1b[0m',"Removing "+index+" from "+image);
|
||||
thisStep = images[image].steps[index];
|
||||
thisStep.UI.onRemove(thisStep.options.step);
|
||||
images[image].steps.splice(index,1);
|
||||
}
|
||||
//tell the UI a step has been removed
|
||||
}
|
||||
|
||||
|
||||
function removeSteps(image,index) {
|
||||
var run = {}, indices;
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
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 = {};
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
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) {
|
||||
log('\x1b[32m%s\x1b[0m',"Running the Sequencer!");
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
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);
|
||||
|
||||
for (var i in json_q.images)
|
||||
require('./LoadImage')(this,i,json_q.images[i])
|
||||
|
||||
json_q.callback();
|
||||
return {
|
||||
|
||||
var ret = {
|
||||
name: "ImageSequencer Wrapper",
|
||||
sequencer: this,
|
||||
addSteps: this.addSteps,
|
||||
removeSteps: this.removeSteps,
|
||||
insertSteps: this.insertSteps,
|
||||
run: this.run,
|
||||
UI: this.UI,
|
||||
setUI: this.setUI,
|
||||
images: loadedimages
|
||||
};
|
||||
|
||||
function load(i) {
|
||||
if(i==loadedimages.length) {
|
||||
json_q.callback.call(ret);
|
||||
return;
|
||||
}
|
||||
var img = loadedimages[i];
|
||||
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('./ui/UserInterface')(UI);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
else modulesdata = modules[name][1];
|
||||
return modulesdata;
|
||||
}
|
||||
|
||||
return {
|
||||
//literals and objects
|
||||
name: "ImageSequencer",
|
||||
options: options,
|
||||
inputlog: inputlog,
|
||||
modules: modules,
|
||||
images: images,
|
||||
events: events,
|
||||
|
||||
//user functions
|
||||
loadImages: loadImages,
|
||||
loadImage: loadImages,
|
||||
addSteps: addSteps,
|
||||
@@ -167,14 +213,15 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
insertSteps: insertSteps,
|
||||
replaceImage: replaceImage,
|
||||
run: run,
|
||||
inputlog: inputlog,
|
||||
modules: modules,
|
||||
images: images,
|
||||
ui: options.ui,
|
||||
setUI: setUI,
|
||||
exportBin: exportBin,
|
||||
modulesInfo: modulesInfo,
|
||||
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
module.exports = ImageSequencer;
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
// 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_) {
|
||||
ref.log('\x1b[36m%s\x1b[0m','inserting step \"' + name + '\" to \"' + image + '\" at \"'+index+'\".');
|
||||
|
||||
var o = ref.copy(o_);
|
||||
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o.name || name;
|
||||
o.selector = o.selector || 'ismod-' + name;
|
||||
o.container = o.container || ref.options.selector;
|
||||
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
|
||||
o.name = o_.name || name;
|
||||
o.selector = o_.selector || 'ismod-' + name;
|
||||
o.container = o_.container || ref.options.selector;
|
||||
o.image = image;
|
||||
|
||||
if(index==-1) index = ref.images[image].steps.length;
|
||||
|
||||
var module = ref.modules[name](o);
|
||||
ref.images[image].steps.splice(index, 0, module);
|
||||
|
||||
function defaultSetupModule() {
|
||||
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
|
||||
selector: o.selector,
|
||||
title: module.options.title,
|
||||
id: o.id
|
||||
});
|
||||
}
|
||||
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
|
||||
else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?)
|
||||
|
||||
// tell the UI that a step has been inserted.
|
||||
o.step = {
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
url: o.url,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](o,UI);
|
||||
ref.images[image].steps.splice(index,0,module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
function LoadImage(ref, name, src) {
|
||||
function CImage(src) {
|
||||
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
|
||||
var image = {
|
||||
src: datauri,
|
||||
format: datauri.split(':')[1].split(';')[0].split('/')[1]
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
function loadImage(name, src) {
|
||||
var image = {
|
||||
src: src,
|
||||
steps: [{
|
||||
options: {
|
||||
id: ref.options.sequencerCounter++,
|
||||
name: "load-image",
|
||||
title: "Load Image"
|
||||
},
|
||||
draw: function() {
|
||||
if(arguments.length==1){
|
||||
this.output = CImage(arguments[0]);
|
||||
return true;
|
||||
}
|
||||
else if(arguments.length==2) {
|
||||
this.output = CImage(arguments[0]);
|
||||
arguments[1]();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
output: CImage(src)
|
||||
}]
|
||||
};
|
||||
ref.images[name] = image;
|
||||
}
|
||||
|
||||
return loadImage(name,src);
|
||||
}
|
||||
|
||||
module.exports = LoadImage;
|
||||
@@ -1,12 +1,44 @@
|
||||
/*
|
||||
* Core modules
|
||||
*/
|
||||
* Core modules and their info files
|
||||
*/
|
||||
module.exports = {
|
||||
'do-nothing': require('./modules/DoNothing/Module'),
|
||||
'green-channel': require('./modules/GreenChannel/Module'),
|
||||
'ndvi-red': require('./modules/NdviRed/Module'),
|
||||
'do-nothing-pix': require('./modules/DoNothingPix/Module.js'),
|
||||
'invert': require('./modules/Invert/Module'),
|
||||
'crop': require('./modules/Crop/Module'),
|
||||
'segmented-colormap': require('./modules/SegmentedColormap/Module')
|
||||
'channel': [
|
||||
require('./modules/Channel/Module'),require('./modules/Channel/info')
|
||||
],
|
||||
'brightness': [
|
||||
require('./modules/Brightness/Module'),require('./modules/Brightness/info')
|
||||
],
|
||||
'edge-detect':[
|
||||
require('./modules/EdgeDetect/Module'),require('./modules/EdgeDetect/info')
|
||||
],
|
||||
'ndvi': [
|
||||
require('./modules/Ndvi/Module'),require('./modules/Ndvi/info')
|
||||
],
|
||||
'invert': [
|
||||
require('./modules/Invert/Module'),require('./modules/Invert/info')
|
||||
],
|
||||
'crop': [
|
||||
require('./modules/Crop/Module'),require('./modules/Crop/info')
|
||||
],
|
||||
'colormap': [
|
||||
require('./modules/Colormap/Module'),require('./modules/Colormap/info')
|
||||
],
|
||||
'decode-qr': [
|
||||
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
|
||||
],
|
||||
'fisheye-gl': [
|
||||
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
|
||||
],
|
||||
'dynamic': [
|
||||
require('./modules/Dynamic/Module'),require('./modules/Dynamic/info')
|
||||
],
|
||||
'blur': [
|
||||
require('./modules/Blur/Module'),require('./modules/Blur/info')
|
||||
],
|
||||
'saturation': [
|
||||
require('./modules/Saturation/Module'),require('./modules/Saturation/info')
|
||||
],
|
||||
'average': [
|
||||
require('./modules/Average/Module'),require('./modules/Average/info')
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
// Uses a given image as input and replaces it with the output.
|
||||
// Works only in the browser.
|
||||
function ReplaceImage(ref,selector,steps,options) {
|
||||
if(!ref.options.inBrowser) return false; // This isn't for Node.js
|
||||
var tempSequencer = ImageSequencer({ui: false});
|
||||
var this_ = ref;
|
||||
var input = document.querySelectorAll(selector);
|
||||
if (window.hasOwnProperty('$')) var input = $(selector);
|
||||
else var input = document.querySelectorAll(selector);
|
||||
var images = [];
|
||||
for (var i = 0; i < input.length; i++)
|
||||
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;
|
||||
var ext = url.split('.').pop();
|
||||
}
|
||||
|
||||
function replaceImage (img, steps) {
|
||||
var url = img.src;
|
||||
// refactor to filetypeFromUrl()
|
||||
var ext = url.split('?')[0].split('.').pop();
|
||||
|
||||
var xmlHTTP = new XMLHttpRequest();
|
||||
xmlHTTP.open('GET', url, true);
|
||||
xmlHTTP.responseType = 'arraybuffer';
|
||||
xmlHTTP.onload = function(e) {
|
||||
var arr = new Uint8Array(this.response);
|
||||
var raw = String.fromCharCode.apply(null,arr);
|
||||
|
||||
// in chunks to avoid "RangeError: Maximum call stack exceeded"
|
||||
// https://github.com/publiclab/image-sequencer/issues/241
|
||||
// https://stackoverflow.com/a/20048852/1116657
|
||||
var raw = '';
|
||||
var i,j,subArray,chunk = 5000;
|
||||
for (i=0,j=arr.length; i<j; i+=chunk) {
|
||||
subArray = arr.subarray(i,i+chunk);
|
||||
raw += String.fromCharCode.apply(null, subArray);
|
||||
}
|
||||
|
||||
var base64 = btoa(raw);
|
||||
var dataURL="data:image/"+ext+";base64," + base64;
|
||||
make(dataURL);
|
||||
@@ -25,11 +41,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;
|
||||
|
||||
39
src/Run.js
@@ -1,47 +1,56 @@
|
||||
function Run(ref, json_q, callback) {
|
||||
function drawStep(drawarray,pos) {
|
||||
if(pos==drawarray.length) {
|
||||
function Run(ref, json_q, callback,progressObj) {
|
||||
if(!progressObj) progressObj = {stop: function(){}}
|
||||
|
||||
function drawStep(drawarray, pos) {
|
||||
if (pos == drawarray.length && drawarray[pos - 1] !== undefined) {
|
||||
var image = drawarray[pos-1].image;
|
||||
if(ref.objTypeOf(callback)=='Function'){
|
||||
if(ref.objTypeOf(callback) == 'Function') {
|
||||
var steps = ref.images[image].steps;
|
||||
var out = steps[steps.length-1].output.src;
|
||||
callback(out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var image = drawarray[pos].image;
|
||||
var i = drawarray[pos].i;
|
||||
var input = ref.images[image].steps[i-1].output;
|
||||
ref.images[image].steps[i].draw(ref.copy(input),function(){
|
||||
drawStep(drawarray,++pos);
|
||||
});
|
||||
// so we don't run on the loadImage module:
|
||||
if (drawarray[pos] !== undefined) {
|
||||
var image = drawarray[pos].image;
|
||||
var i = drawarray[pos].i;
|
||||
var input = ref.images[image].steps[i - 1].output;
|
||||
ref.images[image].steps[i].draw(ref.copy(input), function onEachStep() {
|
||||
drawStep(drawarray, ++pos);
|
||||
},progressObj);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSteps(json_q) {
|
||||
var drawarray = [];
|
||||
for (var image in json_q) {
|
||||
var no_steps = ref.images[image].steps.length;
|
||||
var init = json_q[image];
|
||||
for(var i = 0; i < no_steps-init; i++) {
|
||||
drawarray.push({image: image,i: init+i});
|
||||
for(var i = 0; i < no_steps - init; i++) {
|
||||
drawarray.push({ image: image, i: init + i });
|
||||
}
|
||||
}
|
||||
drawStep(drawarray,0);
|
||||
}
|
||||
|
||||
function filter(json_q){
|
||||
for (var image in json_q) {
|
||||
if (json_q[image]==0 && ref.images[image].steps.length==1)
|
||||
if (json_q[image] == 0 && ref.images[image].steps.length == 1)
|
||||
delete json_q[image];
|
||||
else if (json_q[image]==0) json_q[image]++;
|
||||
else if (json_q[image] == 0) json_q[image]++;
|
||||
}
|
||||
for (var image in json_q) {
|
||||
var prevstep = ref.images[image].steps[json_q[image]-1];
|
||||
var prevstep = ref.images[image].steps[json_q[image] - 1];
|
||||
while (typeof(prevstep) == "undefined" || typeof(prevstep.output) == "undefined") {
|
||||
prevstep = ref.images[image].steps[(--json_q[image]) - 1];
|
||||
}
|
||||
}
|
||||
return json_q;
|
||||
}
|
||||
|
||||
var json_q = filter(json_q);
|
||||
return drawSteps(json_q);
|
||||
|
||||
}
|
||||
module.exports = Run;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Default UI for each image-sequencer module
|
||||
*/
|
||||
module.exports = function UserInterface(options) {
|
||||
|
||||
options = options || {};
|
||||
options.container = options.container || ".panels";
|
||||
options.id = options.id;
|
||||
options.instanceName = options.instanceName;
|
||||
options.random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000);
|
||||
options.uniqueSelector = options.uniqueSelector || options.selector + '-' + options.random;
|
||||
$(options.container).append('<div class="panel ' + options.selector + ' ' + options.uniqueSelector + '" id="sequencer-'+options.id+'"><div class="image"></div></div>');
|
||||
options.el = options.el || $('.' + options.uniqueSelector);
|
||||
createLabel(options.el);
|
||||
|
||||
// method to remove the UI for a given method, and remove the step
|
||||
function display(image) {
|
||||
options.el.find('.image').html(image);
|
||||
}
|
||||
|
||||
// method to remove the UI for a given method, and remove the step
|
||||
function remove() {
|
||||
$('div#sequencer-'+options.id).remove();
|
||||
}
|
||||
|
||||
// method to reorder steps, and update the UI
|
||||
//function move() {}
|
||||
|
||||
function createLabel(el) {
|
||||
if (options.title) el.prepend('<h3 class="title">' + options.title + '</h3> <button class="btn btn-default" onclick="'+options.instanceName+'.removeStep('+options.id+')">Remove Step</button>');
|
||||
}
|
||||
|
||||
return {
|
||||
el: options.el,
|
||||
uniqueSelector: options.uniqueSelector,
|
||||
selector: options.selector,
|
||||
display: display,
|
||||
remove: remove
|
||||
}
|
||||
|
||||
}
|
||||
86
src/modules/Average/Module.js
Executable file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Average all pixel colors
|
||||
*/
|
||||
module.exports = function Average(options, UI){
|
||||
options = options || {};
|
||||
options.blur = options.blur || 2
|
||||
|
||||
//Tell the UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
options.step.metadata = options.step.metadata || {};
|
||||
|
||||
function draw(input,callback,progressObj){
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
// Tell the UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a){
|
||||
return [r,g,b,a]
|
||||
}
|
||||
|
||||
// do the averaging
|
||||
function extraManipulation(pixels){
|
||||
var sum = [0,0,0,0];
|
||||
for (var i = 0; i < pixels.data.length; i += 4) {
|
||||
sum[0] += pixels.data[i + 0];
|
||||
sum[1] += pixels.data[i + 1];
|
||||
sum[2] += pixels.data[i + 2];
|
||||
sum[3] += pixels.data[i + 3];
|
||||
}
|
||||
|
||||
sum[0] = parseInt(sum[0] / (pixels.data.length / 4));
|
||||
sum[1] = parseInt(sum[1] / (pixels.data.length / 4));
|
||||
sum[2] = parseInt(sum[2] / (pixels.data.length / 4));
|
||||
sum[3] = parseInt(sum[3] / (pixels.data.length / 4));
|
||||
|
||||
for (var i = 0; i < pixels.data.length; i += 4) {
|
||||
pixels.data[i + 0] = sum[0];
|
||||
pixels.data[i + 1] = sum[1];
|
||||
pixels.data[i + 2] = sum[2];
|
||||
pixels.data[i + 3] = sum[3];
|
||||
}
|
||||
// report back and store average in metadata:
|
||||
options.step.metadata.averages = sum;
|
||||
console.log("average: ", sum);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype){
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {
|
||||
src: datauri,
|
||||
format: mimetype
|
||||
};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
6
src/modules/Average/info.json
Executable file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Average",
|
||||
"description": "Average all pixel color",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/modules/Channel/Module.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Display only one color channel
|
||||
*/
|
||||
module.exports = function Channel(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.channel = options.channel || "green";
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback,progressObj) {
|
||||
|
||||
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) {
|
||||
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){
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/modules/Colormap/Colormap.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Accepts a value from 0-255 and returns the new color-mapped pixel
|
||||
* from a lookup table, which can be specified as an array of [begin, end]
|
||||
* gradients, where begin and end are represented as [r, g, b] colors. In
|
||||
* combination, a lookup table which maps values from 0 - 255 smoothly from black to white looks like:
|
||||
* [
|
||||
* [0, [0, 0, 0], [255, 255, 255]],
|
||||
* [1, [255, 255, 255], [255, 255, 255]]
|
||||
* ]
|
||||
*
|
||||
* Adapted from bgamari's work in Infragram: https://github.com/p-v-o-s/infragram-js/commit/346c97576a07b71a55671d17e0153b7df74e803b
|
||||
*/
|
||||
|
||||
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 = colormap(options.colormap);
|
||||
// if a stored colormap is named with a string like "fastie":
|
||||
else if(colormaps.hasOwnProperty(options.colormap))
|
||||
colormapFunction = colormaps[options.colormap];
|
||||
else colormapFunction = colormaps.default;
|
||||
return colormapFunction(value / 255.00);
|
||||
}
|
||||
|
||||
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];
|
||||
_ref1 = [segments[0][0], 1], x0 = _ref1[0], x1 = _ref1[1];
|
||||
if (x < x0) {
|
||||
return y0;
|
||||
}
|
||||
for (i = _i = 0, _len = segments.length; _i < _len; i = ++_i) {
|
||||
_ref2 = segments[i], xstart = _ref2[0], y0 = _ref2[1], y1 = _ref2[2];
|
||||
x0 = xstart;
|
||||
if (i === segments.length - 1) {
|
||||
x1 = 1;
|
||||
break;
|
||||
}
|
||||
x1 = segments[i + 1][0];
|
||||
if ((xstart <= x && x < x1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = [];
|
||||
for (i = _j = 0, _ref3 = y0.length; 0 <= _ref3 ? _j < _ref3 : _j > _ref3; i = 0 <= _ref3 ? ++_j : --_j) {
|
||||
result[i] = (x - x0) / (x1 - x0) * (y1[i] - y0[i]) + y0[i];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
var colormaps = {
|
||||
greyscale: colormap([
|
||||
[0, [0, 0, 0], [255, 255, 255] ],
|
||||
[1, [255, 255, 255], [255, 255, 255] ]
|
||||
]),
|
||||
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: 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: 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: colormap([
|
||||
[0, [255, 255, 255], [0, 0, 0] ],
|
||||
[0.167, [0, 0, 0], [255, 255, 255] ],
|
||||
[0.33, [255, 255, 255], [0, 0, 0] ],
|
||||
[0.5, [0, 0, 0], [140, 140, 255] ],
|
||||
[0.55, [140, 140, 255], [0, 255, 0] ],
|
||||
[0.63, [0, 255, 0], [255, 255, 0] ],
|
||||
[0.75, [255, 255, 0], [255, 0, 0] ],
|
||||
[0.95, [255, 0, 0], [255, 0, 255] ]
|
||||
])
|
||||
}
|
||||
54
src/modules/Colormap/Module.js
Normal file
@@ -0,0 +1,54 @@
|
||||
module.exports = function Colormap(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
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;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
var combined = (r + g + b) / 3.000;
|
||||
var res = require('./Colormap')(combined, options);
|
||||
return [res[0], res[1], res[2], 255];
|
||||
}
|
||||
|
||||
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,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
module.exports = function Crop(input,options,callback) {
|
||||
|
||||
var getPixels = require("get-pixels"),
|
||||
savePixels = require("save-pixels"),
|
||||
base64 = require('base64-stream');
|
||||
var getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
options.x = parseInt(options.x) || 0;
|
||||
options.y = parseInt(options.y) || 0;
|
||||
|
||||
getPixels(input.src,function(err,pixels){
|
||||
var newdata = [];
|
||||
var ox = options.x || 0;
|
||||
var oy = options.y || 0;
|
||||
var w = options.w || Math.floor(0.5*pixels.shape[0]);
|
||||
var h = options.h || Math.floor(0.5*pixels.shape[1]);
|
||||
options.w = parseInt(options.w) || Math.floor(0.5*pixels.shape[0]);
|
||||
options.h = parseInt(options.h) || Math.floor(0.5*pixels.shape[1]);
|
||||
var ox = options.x;
|
||||
var oy = options.y;
|
||||
var w = options.w;
|
||||
var h = options.h;
|
||||
var iw = pixels.shape[0]; //Width of Original Image
|
||||
newarray = new Uint8Array(4*w*h);
|
||||
var newarray = new Uint8Array(4*w*h);
|
||||
for (var n = oy; n < oy + h; n++) {
|
||||
newarray.set(pixels.data.slice(n*4*iw + ox, n*4*iw + ox + 4*w),4*w*(n-oy));
|
||||
}
|
||||
@@ -19,15 +22,21 @@ module.exports = function Crop(input,options,callback) {
|
||||
pixels.shape = [w,h,4];
|
||||
pixels.stride[1] = 4*w;
|
||||
|
||||
options.format = "jpeg";
|
||||
options.format = input.format;
|
||||
|
||||
w = base64.encode();
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format);
|
||||
r.pipe(w).on('finish',function(){
|
||||
data = w.read().toString();
|
||||
datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
|
||||
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;
|
||||
callback(datauri,options.format);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,30 +13,65 @@
|
||||
* y = options.y
|
||||
* y = options.y + options.h
|
||||
*/
|
||||
module.exports = function CropModule(options) {
|
||||
options = options || {};
|
||||
options.title = "Crop Image";
|
||||
var this_ = this;
|
||||
var output
|
||||
module.exports = function CropModule(options, UI) {
|
||||
|
||||
function draw(input,callback) {
|
||||
// TODO: we could also set this to {} if nil in AddModule.js to avoid this line:
|
||||
options = options || {};
|
||||
|
||||
const this_ = this;
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step); // we should get UI to return the image thumbnail so we can attach our own UI extensions
|
||||
|
||||
require('./Crop')(input,options,function(out,format){
|
||||
this_.output = {
|
||||
src: out,
|
||||
format: format
|
||||
}
|
||||
callback();
|
||||
});
|
||||
// add our custom in-module html ui:
|
||||
if (options.step.inBrowser) var ui = require('./Ui.js')(options.step, UI);
|
||||
var output,
|
||||
setupComplete = false;
|
||||
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
}
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
// save the input image;
|
||||
// TODO: this should be moved to module API to persist the input image
|
||||
options.step.input = input.src;
|
||||
|
||||
require('./Crop')(input, options, function(out, format){
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: out,
|
||||
format: format
|
||||
}
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = out;
|
||||
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
|
||||
// we should do this via event/listener:
|
||||
if (ui && ui.hide) ui.hide();
|
||||
|
||||
// start custom UI setup (draggable UI)
|
||||
// only once we have an input image
|
||||
if (setupComplete === false && options.step.inBrowser) {
|
||||
setupComplete = true;
|
||||
ui.setup();
|
||||
}
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
97
src/modules/Crop/Ui.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// hide on save
|
||||
module.exports = function CropModuleUi(step, ui) {
|
||||
|
||||
let inputWidth = 0,
|
||||
inputHeight = 0;
|
||||
|
||||
// We don't have input image dimensions at the
|
||||
// time of setting up the UI; that comes when draw() is triggered.
|
||||
// So we trigger setup only on first run of draw()
|
||||
// TODO: link this to an event rather than an explicit call in Module.js
|
||||
function setup() {
|
||||
let x = 0,
|
||||
y = 0;
|
||||
|
||||
// display original uncropped input image on initial setup
|
||||
showOriginal()
|
||||
|
||||
inputWidth = Math.floor(imgEl().naturalWidth);
|
||||
inputHeight = Math.floor(imgEl().naturalHeight);
|
||||
|
||||
// display with 50%/50% default crop:
|
||||
setOptions(x, y, inputWidth, inputHeight);
|
||||
|
||||
$(imgEl()).imgAreaSelect({
|
||||
handles: true,
|
||||
x1: x,
|
||||
y1: y,
|
||||
x2: x + inputWidth / 2,
|
||||
y2: y + inputHeight / 2,
|
||||
// when selection is complete
|
||||
onSelectEnd: function onSelectEnd(img, selection) {
|
||||
// assign crop values to module UI form inputs:
|
||||
let converted = convertToNatural(
|
||||
selection.x1,
|
||||
selection.y1,
|
||||
selection.width,
|
||||
selection.height
|
||||
);
|
||||
setOptions(
|
||||
converted[0],
|
||||
converted[1],
|
||||
converted[2],
|
||||
converted[3]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function convertToNatural(_x, _y, _width, _height) {
|
||||
let displayWidth = $(imgEl()).width(),
|
||||
displayHeight = $(imgEl()).height();
|
||||
// return in same order [ x, y, width, height ]:
|
||||
return [
|
||||
Math.floor(( _x / displayWidth ) * inputWidth),
|
||||
Math.floor(( _y / displayHeight ) * inputHeight),
|
||||
Math.floor(( _width / displayWidth ) * inputWidth),
|
||||
Math.floor(( _height / displayHeight ) * inputHeight)
|
||||
]
|
||||
}
|
||||
|
||||
function remove() {
|
||||
$(imgEl()).imgAreaSelect({
|
||||
remove: true
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
// then hide the draggable UI
|
||||
$(imgEl()).imgAreaSelect({
|
||||
hide: true
|
||||
});
|
||||
}
|
||||
|
||||
// step.imgSelector is not defined, imgElement is:
|
||||
function imgEl() {
|
||||
return step.imgElement;
|
||||
}
|
||||
|
||||
function setOptions(x1, y1, width, height) {
|
||||
let options = $($(imgEl()).parents()[2]).find("input");
|
||||
options[0].value = x1;
|
||||
options[1].value = y1;
|
||||
options[2].value = width;
|
||||
options[3].value = height;
|
||||
}
|
||||
|
||||
// replaces currently displayed output thumbnail with the input image, for ui dragging purposes
|
||||
function showOriginal() {
|
||||
step.imgElement.src = step.input;
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup,
|
||||
remove: remove,
|
||||
hide: hide
|
||||
}
|
||||
}
|
||||
27
src/modules/Crop/info.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "Crop",
|
||||
"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": {
|
||||
"type": "integer",
|
||||
"desc": "X-position (measured from left) from where cropping starts",
|
||||
"default": 0
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"desc": "Y-position (measured from top) from where cropping starts",
|
||||
"default": 0
|
||||
},
|
||||
"w": {
|
||||
"type": "integer",
|
||||
"desc": "Width of crop",
|
||||
"default": "(50%)"
|
||||
},
|
||||
"h": {
|
||||
"type": "integer",
|
||||
"desc": "Height of crop",
|
||||
"default": "(50%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/modules/DecodeQr/Module.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Decodes QR from a given image.
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
|
||||
var output;
|
||||
var jsQR = require('jsqr');
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
// This function is called everytime a step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
getPixels(input.src,function(err,pixels){
|
||||
|
||||
if(err) throw err;
|
||||
|
||||
var w = pixels.shape[0];
|
||||
var h = pixels.shape[1];
|
||||
var decoded = jsQR.decodeQRFromImage(pixels.data,w,h);
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = input;
|
||||
step.output.data = decoded;
|
||||
|
||||
// Tell Image Sequencer that this step is complete
|
||||
callback();
|
||||
|
||||
// These values are accessible to the UI
|
||||
options.step.output = input.src;
|
||||
options.step.qrval = decoded;
|
||||
|
||||
// Tell the UI that the step is complete and output is set
|
||||
UI.onComplete(options.step);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
11
src/modules/DecodeQr/info.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Decode QR",
|
||||
"description": "Search for and decode a QR code in the image",
|
||||
"inputs": {
|
||||
},
|
||||
"outputs": {
|
||||
"qrval": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Demo Module. Does nothing. Adds a step where output is equal to input.
|
||||
*/
|
||||
module.exports = function DoNothing(options) {
|
||||
options = options || {};
|
||||
options.title = "Do Nothing";
|
||||
var this_ = this;
|
||||
var output
|
||||
|
||||
function draw(input,callback) {
|
||||
this.output = input;
|
||||
callback();
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This module extracts pixels and saves them as it is.
|
||||
*/
|
||||
module.exports = function DoNothingPix(options) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Do Nothing with pixels";
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
95
src/modules/Dynamic/Module.js
Normal file
@@ -0,0 +1,95 @@
|
||||
module.exports = function Dynamic(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
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 ';
|
||||
func = func + expression + '}';
|
||||
var f;
|
||||
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.green_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,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
26
src/modules/Dynamic/info.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"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",
|
||||
"desc": "Expression to return for red channel with R, G, B, and A inputs",
|
||||
"default": "r"
|
||||
},
|
||||
"green": {
|
||||
"type": "input",
|
||||
"desc": "Expression to return for green channel with R, G, B, and A inputs",
|
||||
"default": "g"
|
||||
},
|
||||
"blue": {
|
||||
"type": "input",
|
||||
"desc": "Expression to return for blue channel with R, G, B, and A inputs",
|
||||
"default": "b"
|
||||
},
|
||||
"monochrome (fallback)": {
|
||||
"type": "input",
|
||||
"desc": "Expression to return with R, G, B, and A inputs; fallback for other channels if none provided",
|
||||
"default": "r + g + b"
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/modules/FisheyeGl/Module.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Resolves Fisheye Effect
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
var output;
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
require('fisheyegl');
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
if (!options.inBrowser) { // This module is only for browser
|
||||
this.output = input;
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
// Create a canvas, if it doesn't already exist.
|
||||
if (!document.querySelector('#image-sequencer-canvas')) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.style.display = "none";
|
||||
canvas.setAttribute('id','image-sequencer-canvas');
|
||||
document.body.append(canvas);
|
||||
}
|
||||
else var canvas = document.querySelector('#image-sequencer-canvas');
|
||||
|
||||
distorter = FisheyeGl({
|
||||
selector: "#image-sequencer-canvas"
|
||||
});
|
||||
|
||||
// Parse the inputs
|
||||
options.a = parseFloat(options.a) || distorter.lens.a;
|
||||
options.b = parseFloat(options.b) || distorter.lens.b;
|
||||
options.Fx = parseFloat(options.Fx) || distorter.lens.Fx;
|
||||
options.Fy = parseFloat(options.Fy) || distorter.lens.Fy;
|
||||
options.scale = parseFloat(options.scale) || distorter.lens.scale;
|
||||
options.x = parseFloat(options.x) || distorter.fov.x;
|
||||
options.y = parseFloat(options.y) || distorter.fov.y;
|
||||
|
||||
// Set fisheyegl inputs
|
||||
distorter.lens.a = options.a;
|
||||
distorter.lens.b = options.b;
|
||||
distorter.lens.Fx = options.Fx;
|
||||
distorter.lens.Fy = options.Fy;
|
||||
distorter.lens.scale = options.scale;
|
||||
distorter.fov.x = options.x;
|
||||
distorter.fov.y = options.y;
|
||||
|
||||
// generate fisheyegl output
|
||||
distorter.setImage(input.src,function(){
|
||||
|
||||
// this output is accessible to Image Sequencer
|
||||
step.output = {src: canvas.toDataURL(), format: input.format};
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = step.output.src;
|
||||
|
||||
// Tell Image Sequencer and UI that step has been drawn
|
||||
callback();
|
||||
UI.onComplete(options.step);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
66
src/modules/FisheyeGl/info.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"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",
|
||||
"desc": "a parameter",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4
|
||||
},
|
||||
"b": {
|
||||
"type": "float",
|
||||
"desc": "b parameter",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4
|
||||
},
|
||||
"Fx": {
|
||||
"type": "float",
|
||||
"desc": "Fx parameter",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4
|
||||
},
|
||||
"Fy": {
|
||||
"type": "float",
|
||||
"desc": "Fy parameter",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4
|
||||
},
|
||||
"scale": {
|
||||
"type": "float",
|
||||
"desc": "Image Scaling",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"x": {
|
||||
"type": "float",
|
||||
"desc": "FOV x parameter",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"y": {
|
||||
"type": "float",
|
||||
"desc": "FOV y parameter",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"fragmentSrc": {
|
||||
"type": "PATH",
|
||||
"desc": "Patht to a WebGL fragment shader file",
|
||||
"default": "(inbuilt)"
|
||||
},
|
||||
"vertexSrc": {
|
||||
"type": "PATH",
|
||||
"desc": "Patht to a WebGL vertex shader file",
|
||||
"default": "(inbuilt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Display only the green channel
|
||||
*/
|
||||
module.exports = function GreenChannel(options) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Green channel only";
|
||||
options.description = "Displays only the green channel of an image";
|
||||
var output;
|
||||
|
||||
//function setup() {} // optional
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
return [0, g, 0, a];
|
||||
}
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
//setup: setup, // optional
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,55 @@
|
||||
/*
|
||||
* Display only the green channel
|
||||
* Invert the image
|
||||
*/
|
||||
module.exports = function GreenChannel(options) {
|
||||
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;
|
||||
|
||||
//function setup() {} // optional
|
||||
// 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;
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
return [255-r, 255-g, 255-b, a];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format: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,
|
||||
//setup: setup, // optional
|
||||
draw: draw,
|
||||
output: output
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
6
src/modules/Invert/info.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Invert",
|
||||
"description": "Inverts the image.",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
59
src/modules/Ndvi/Module.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* NDVI with red filter (blue channel is infrared)
|
||||
*/
|
||||
module.exports = function Ndvi(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
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,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) {
|
||||
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];
|
||||
}
|
||||
|
||||
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 step has been drawn succesfully.
|
||||
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
|
||||
}
|
||||
}
|
||||
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,33 +0,0 @@
|
||||
/*
|
||||
* NDVI with red filter (blue channel is infrared)
|
||||
*/
|
||||
module.exports = function NdviRed(options) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "NDVI for red-filtered cameras (blue is infrared)";
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
var ndvi = (b - r) / (1.00 * b + r);
|
||||
var x = 255 * (ndvi + 1) / 2;
|
||||
return [x, x, x, a];
|
||||
}
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw
|
||||
}
|
||||
}
|
||||
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,32 +0,0 @@
|
||||
module.exports = function SegmentedColormap(options) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Segmented Colormap";
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
var ndvi = (b - r) / (r + b);
|
||||
var normalized = (ndvi + 1) / 2;
|
||||
var res = require('./SegmentedColormap')(normalized,options);
|
||||
return [res[0], res[1], res[2], 255];
|
||||
}
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Accepts a normalized ndvi and returns the new color-mapped pixel
|
||||
*/
|
||||
|
||||
module.exports = function SegmentedColormap(normalized,options) {
|
||||
options.colormap = options.colormap || "default";
|
||||
if(typeof(options.colormap) == "object")
|
||||
colormapFunction = segmented_colormap(options.colormap);
|
||||
else if(colormaps.hasOwnProperty(options.colormap))
|
||||
colormapFunction = colormaps[options.colormap];
|
||||
else colormapFunction = colormaps.default;
|
||||
|
||||
return colormapFunction(normalized);
|
||||
}
|
||||
|
||||
function segmented_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];
|
||||
_ref1 = [segments[0][0], 1], x0 = _ref1[0], x1 = _ref1[1];
|
||||
if (x < x0) {
|
||||
return y0;
|
||||
}
|
||||
for (i = _i = 0, _len = segments.length; _i < _len; i = ++_i) {
|
||||
_ref2 = segments[i], xstart = _ref2[0], y0 = _ref2[1], y1 = _ref2[2];
|
||||
x0 = xstart;
|
||||
if (i === segments.length - 1) {
|
||||
x1 = 1;
|
||||
break;
|
||||
}
|
||||
x1 = segments[i + 1][0];
|
||||
if ((xstart <= x && x < x1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = [];
|
||||
for (i = _j = 0, _ref3 = y0.length; 0 <= _ref3 ? _j < _ref3 : _j > _ref3; i = 0 <= _ref3 ? ++_j : --_j) {
|
||||
result[i] = (x - x0) / (x1 - x0) * (y1[i] - y0[i]) + y0[i];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
var greyscale_colormap = segmented_colormap([[0, [0, 0, 0], [255, 255, 255]], [1, [255, 255, 255], [255, 255, 255]]]);
|
||||
|
||||
var default_colormap = segmented_colormap([[0, [0, 0, 255], [38, 195, 195]], [0.5, [0, 150, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 50, 50]]]);
|
||||
|
||||
var stretched_colormap = segmented_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]]]);
|
||||
|
||||
var fastie_colormap = segmented_colormap([[0, [255, 255, 255], [0, 0, 0]], [0.167, [0, 0, 0], [255, 255, 255]], [0.33, [255, 255, 255], [0, 0, 0]], [0.5, [0, 0, 0], [140, 140, 255]], [0.55, [140, 140, 255], [0, 255, 0]], [0.63, [0, 255, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 0, 0]], [0.95, [255, 0, 0], [255, 0, 255]]]);
|
||||
|
||||
var colormaps = {
|
||||
greyscale: greyscale_colormap,
|
||||
default: default_colormap,
|
||||
stretched: stretched_colormap,
|
||||
fastie: fastie_colormap
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
module.exports = function ImageThreshold(options) {
|
||||
options = options || {};
|
||||
options.title = "Threshold image";
|
||||
options.threshold = options.threshold || 30;
|
||||
|
||||
var image;
|
||||
|
||||
6
src/modules/Threshold/info.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Threshold image",
|
||||
"description": "...",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,87 @@
|
||||
/*
|
||||
* 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"),
|
||||
base64 = require('base64-stream');
|
||||
|
||||
|
||||
var getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
getPixels(image.src, function(err, pixels) {
|
||||
|
||||
|
||||
if(err) {
|
||||
console.log("Bad image path")
|
||||
return
|
||||
console.log('Bad image path', image);
|
||||
return;
|
||||
}
|
||||
|
||||
if(options.getNeighbourPixel){
|
||||
options.getNeighbourPixel.fun = function getNeighborPixel(distX,distY) {
|
||||
return options.getNeighbourPixel(pixels,x,y,distX,distY);
|
||||
};
|
||||
}
|
||||
|
||||
// iterate through pixels;
|
||||
// this could possibly be more efficient; see
|
||||
// TODO: this could possibly be more efficient; see
|
||||
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181
|
||||
for(var x = 0; x < pixels.shape[0]; x++) {
|
||||
for(var y = 0; y < pixels.shape[1]; y++) {
|
||||
|
||||
pixel = options.changePixel(
|
||||
pixels.get(x, y, 0),
|
||||
pixels.get(x, y, 1),
|
||||
pixels.get(x, y, 2),
|
||||
pixels.get(x, y, 3)
|
||||
);
|
||||
|
||||
|
||||
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.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()
|
||||
}
|
||||
}
|
||||
|
||||
options.format = "jpeg";
|
||||
|
||||
// perform any extra operations on the entire array:
|
||||
if (options.extraManipulation)
|
||||
pixels = options.extraManipulation(pixels);
|
||||
|
||||
// there may be a more efficient means to encode an image object,
|
||||
// but node modules and their documentation are essentially arcane on this point
|
||||
w = base64.encode();
|
||||
var r = savePixels(pixels, options.format);
|
||||
r.pipe(w).on('finish',function(){
|
||||
data = w.read().toString();
|
||||
datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format, {quality: 100});
|
||||
|
||||
r.on('data', function(chunk){
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
r.on('end', function(){
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
if (options.output) options.output(options.image,datauri,options.format);
|
||||
if (options.callback) options.callback();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
105
src/ui/LoadImage.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// special module to load an image into the start of the sequence; used in the HTML UI
|
||||
function LoadImage(ref, name, src, main_callback) {
|
||||
function makeImage(datauri) {
|
||||
var image = {
|
||||
src: datauri,
|
||||
format: datauri.split(':')[1].split(';')[0].split('/')[1]
|
||||
}
|
||||
return image;
|
||||
}
|
||||
function CImage(src, callback) {
|
||||
var datauri;
|
||||
if (!!src.match(/^data:/i)) {
|
||||
datauri = src;
|
||||
callback(datauri);
|
||||
}
|
||||
else if (!ref.options.inBrowser && !!src.match(/^https?:\/\//i)) {
|
||||
require( src.match(/^(https?):\/\//i)[1] ).get(src,function(res){
|
||||
var data = '';
|
||||
var contentType = res.headers['content-type'];
|
||||
res.setEncoding('base64');
|
||||
res.on('data',function(chunk) {data += chunk;});
|
||||
res.on('end',function() {
|
||||
callback("data:"+contentType+";base64,"+data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (ref.options.inBrowser) {
|
||||
var ext = src.split('.').pop();
|
||||
var image = document.createElement('img');
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
image.onload = function() {
|
||||
canvas.width = image.naturalWidth;
|
||||
canvas.height = image.naturalHeight;
|
||||
context.drawImage(image,0,0);
|
||||
datauri = canvas.toDataURL(ext);
|
||||
callback(datauri);
|
||||
}
|
||||
image.src = src;
|
||||
}
|
||||
else {
|
||||
datauri = require('urify')(src);
|
||||
callback(datauri);
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage(name, src) {
|
||||
var step = {
|
||||
name: "load-image",
|
||||
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,
|
||||
ui: ref.options.ui
|
||||
};
|
||||
|
||||
var image = {
|
||||
src: src,
|
||||
steps: [{
|
||||
options: {
|
||||
id: step.ID,
|
||||
name: "load-image",
|
||||
description: "This initial step loads and displays the original image without any modifications.",
|
||||
title: "Load Image",
|
||||
step: step
|
||||
},
|
||||
UI: ref.events,
|
||||
draw: function() {
|
||||
UI.onDraw(options.step);
|
||||
if(arguments.length==1){
|
||||
this.output = CImage(arguments[0]);
|
||||
options.step.output = this.output;
|
||||
UI.onComplete(options.step);
|
||||
return true;
|
||||
}
|
||||
else if(arguments.length==2) {
|
||||
this.output = CImage(arguments[0]);
|
||||
options.step.output = this.output;
|
||||
arguments[1]();
|
||||
UI.onComplete(options.step);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}]
|
||||
};
|
||||
CImage(src, function(datauri) {
|
||||
var output = makeImage(datauri);
|
||||
ref.images[name] = image;
|
||||
var loadImageStep = ref.images[name].steps[0];
|
||||
loadImageStep.output = output;
|
||||
loadImageStep.options.step.output = loadImageStep.output.src;
|
||||
loadImageStep.UI.onSetup(loadImageStep.options.step);
|
||||
loadImageStep.UI.onDraw(loadImageStep.options.step);
|
||||
loadImageStep.UI.onComplete(loadImageStep.options.step);
|
||||
|
||||
main_callback();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return loadImage(name,src);
|
||||
}
|
||||
|
||||
module.exports = LoadImage;
|
||||
58
src/ui/UserInterface.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* User Interface Handling Module
|
||||
*/
|
||||
|
||||
module.exports = function UserInterface(events = {}) {
|
||||
|
||||
events.onSetup = events.onSetup || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
} else if(step.inBrowser) {
|
||||
// Create and append an HTML Element
|
||||
console.log("Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
} else {
|
||||
// Create a NodeJS Object
|
||||
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
events.onDraw = events.onDraw || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
} else if(step.inBrowser) {
|
||||
// Overlay a loading spinner
|
||||
console.log("Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
} else {
|
||||
// Don't do anything
|
||||
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
events.onComplete = events.onComplete || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
} else if(step.inBrowser) {
|
||||
// Update the DIV Element
|
||||
// Hide the laoding spinner
|
||||
console.log("Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
} else {
|
||||
// Update the NodeJS Object
|
||||
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
events.onRemove = events.onRemove || function(step) {
|
||||
if (step.ui == false){
|
||||
// No UI
|
||||
} else if(step.inBrowser) {
|
||||
// Remove the DIV Element
|
||||
console.log("Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
} else {
|
||||
// Delete the NodeJS Object
|
||||
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
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');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: "none" });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
test('loadImages/loadImage has a name generator.', function (t){
|
||||
sequencer.loadImage(red);
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages/loadImage returns a wrapper.', function (t){
|
||||
var returnval = sequencer.loadImage(red);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image2","Image scope is defined");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).addSteps('invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image3","Image scope is defined");
|
||||
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
|
||||
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
|
||||
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
|
||||
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.addSteps("image3","ndvi-red");
|
||||
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.end();
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).addSteps('invert').removeSteps(1);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image4","Image scope is defined");
|
||||
t.equal(sequencer.images.image4.steps.length,1);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.removeSteps("image3",1);
|
||||
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
|
||||
t.equal(sequencer.images.image3.steps.length,2);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).insertSteps(1,'invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image5","Image scope is defined");
|
||||
t.equal(sequencer.images.image5.steps.length,2);
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.insertSteps("image5",1,"ndvi-red");
|
||||
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.end();
|
||||
});
|
||||
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()
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
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');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: "none" });
|
||||
var image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
sequencer.loadImages(image);
|
||||
|
||||
sequencer.addSteps(['do-nothing-pix','invert','invert']);
|
||||
sequencer.run();
|
||||
|
||||
test("Inverted image isn't identical", function (t) {
|
||||
t.notEqual(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[2].output.src);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test("Twice inverted image is identical to original image", function (t) {
|
||||
t.equal(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[3].output.src);
|
||||
t.end();
|
||||
});
|
||||
87
test/modules/chain.js
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
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');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
test('loadImages/loadImage has a name generator.', function (t){
|
||||
sequencer.loadImage(red);
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages/loadImage returns a wrapper in the callback.', function (t){
|
||||
sequencer.loadImage(red, function() {
|
||||
var returnval = this;
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image2","Image scope is defined");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable.', function (t){
|
||||
sequencer.loadImage(red, function(){
|
||||
var returnval = this;
|
||||
this.addSteps('invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image3","Image scope is defined");
|
||||
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
|
||||
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
|
||||
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
|
||||
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable without loadImages.', function (t){
|
||||
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","Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable.', function (t){
|
||||
sequencer.loadImage(red,function(){
|
||||
var returnval = this;
|
||||
this.addSteps('invert').removeSteps(1);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image4","Image scope is defined");
|
||||
t.equal(sequencer.images.image4.steps.length,1);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable without loadImages.', function (t){
|
||||
var returnval = sequencer.removeSteps("image3",1);
|
||||
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
|
||||
t.equal(sequencer.images.image3.steps.length,2);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable.', function (t){
|
||||
sequencer.loadImage(red,function() {
|
||||
var returnval = this;
|
||||
this.insertSteps(1,'invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image5","Image scope is defined");
|
||||
t.equal(sequencer.images.image5.steps.length,2);
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable without loadImages.', function (t){
|
||||
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","Correct Step Inserrted");
|
||||
t.end();
|
||||
});
|
||||
83
test/modules/image-manip.js
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var looksSame = require('looks-same');
|
||||
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 image files as DataURLs so they can be tested alike on browser and Node.
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
|
||||
var qr = require('./images/IS-QR.js');
|
||||
var test_png = require('./images/test.png.js');
|
||||
var test_gif = require('./images/test.gif.js');
|
||||
var spinner = require('ora')('').start()
|
||||
|
||||
sequencer.loadImages(test_png);
|
||||
sequencer.addSteps(['invert','invert']);
|
||||
|
||||
test("Preload", function(t) {
|
||||
sequencer.run(spinner,function(){
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Inverted image isn't identical", function (t) {
|
||||
var step1 = sequencer.images.image1.steps[0].output.src;
|
||||
var step2 = sequencer.images.image1.steps[1].output.src;
|
||||
step1 = DataURItoBuffer(step1);
|
||||
step2 = DataURItoBuffer(step2);
|
||||
looksSame(step1,step2,function(err,res){
|
||||
if(err) console.log(err);
|
||||
t.equal(res,false);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Twice inverted image is identical to original image", function (t) {
|
||||
var step1 = sequencer.images.image1.steps[0].output.src;
|
||||
var step3 = sequencer.images.image1.steps[2].output.src;
|
||||
step1 = DataURItoBuffer(step1);
|
||||
step3 = DataURItoBuffer(step3);
|
||||
looksSame(step1,step3,function(err,res){
|
||||
if(err) console.log(err);
|
||||
t.equal(res,true);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Decode QR module works properly :: setup", function (t) {
|
||||
sequencer.loadImage(qr,function(){
|
||||
this.addSteps('decode-qr').run(spinner.start(),function(){
|
||||
t.end();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
test("Decode QR module works properly :: teardown", function (t) {
|
||||
t.equal("http://github.com/publiclab/image-sequencer",sequencer.images.image2.steps[1].output.data);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test("PixelManipulation works for PNG images", function (t) {
|
||||
sequencer.loadImages(test_png,function(){
|
||||
this.addSteps('invert').run(spinner.start(),function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("PixelManipulation works for GIF images", function (t) {
|
||||
sequencer.loadImages(test_gif,function(){
|
||||
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) {
|
||||
@@ -25,7 +25,7 @@ function copy(g,a) {
|
||||
var parent = (typeof(global)==="undefined")?window:global;
|
||||
var global1 = copy(true,parent);
|
||||
|
||||
var sequencer = ImageSequencer({ ui: "none" });
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
|
||||
test('Image Sequencer has tests', function (t) {
|
||||
@@ -40,18 +40,39 @@ test('loadImages loads a DataURL image and creates a step.', function (t){
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages loads a PATH image and creates a step. (NodeJS)', function (t){
|
||||
if(sequencer.options.inBrowser){
|
||||
t.equal("not applicable","not applicable","Not applicable for Browser");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImages(red);
|
||||
test('modulesInfo() returns info for each module', function (t){
|
||||
var info = sequencer.modulesInfo();
|
||||
t.equal(Object.keys(info).length, Object.keys(sequencer.modules).length);
|
||||
t.equal(info.hasOwnProperty(Object.keys(sequencer.modules)[0]), true);
|
||||
t.equal(info[Object.keys(sequencer.modules)[0]].hasOwnProperty('name'), true);
|
||||
t.equal(info[Object.keys(sequencer.modules)[0]].hasOwnProperty('inputs'), true);
|
||||
t.end();
|
||||
});
|
||||
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){
|
||||
require('dns').resolve('www.github.com', function(err) {
|
||||
if (err) {
|
||||
console.log("Test aborted due to no internet");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImage('URL','https://ccpandhare.github.io/image-sequencer/examples/images/red.jpg', function(){
|
||||
t.equal(sequencer.images.URL.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.URL.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t){
|
||||
sequencer.loadImages('examples/images/red.jpg');
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('loadImage works too.', function (t){
|
||||
sequencer.loadImage('test2',red);
|
||||
@@ -61,38 +82,39 @@ test('loadImage works too.', function (t){
|
||||
});
|
||||
|
||||
test('addSteps("image","name") adds a step', function (t) {
|
||||
sequencer.addSteps('test','do-nothing');
|
||||
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, "do-nothing", "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('do-nothing');
|
||||
sequencer.addSteps('channel');
|
||||
t.equal(sequencer.images.test.steps.length, 3, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[2].options.name, "do-nothing", "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(['do-nothing','do-nothing-pix']);
|
||||
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, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[4].options.name, "do-nothing-pix", "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('do-nothing',{});
|
||||
sequencer.addSteps('channel',{});
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[5].options.name, "do-nothing", "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','do-nothing',{});
|
||||
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, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[6].options.name, "channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -115,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,'do-nothing',{});
|
||||
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, "do-nothing", "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,'do-nothing');
|
||||
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, "do-nothing", "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,'do-nothing');
|
||||
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, "do-nothing", "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:'do-nothing', 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, "do-nothing", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||