Compare commits

...

134 Commits

Author SHA1 Message Date
Jeffrey Warren 376c264168 Update demo.css 2017-09-09 09:17:55 -04:00
Jeffrey Warren 1f3000b7f9 Demo styling tweaks (#105)
* html demo tweaks

* in-progress demo styling

* merge fies

* old demo cleanup
2017-09-06 14:34:24 -04:00
Chinmay Pandhare dc9827049a Merge pull request #109 from ccpandhare/master
Remove const
2017-09-05 20:39:39 +05:30
Chinmay Pandhare f69f8461a7 Remove const 2017-09-05 19:45:06 +05:30
Chinmay Pandhare 5b860dee83 Merge pull request #104 from ccpandhare/master
Changed const to var
2017-09-01 20:22:17 +05:30
Chinmay Pandhare 959f5db6df Merge master 2017-09-01 20:00:33 +05:30
Chinmay Pandhare 237a96247a Remove ES6 const syntax 2017-09-01 19:59:25 +05:30
Chinmay Pandhare 2e4255a5ca Merge pull request #103 from ROODAY/cli-fixes
Cleaned up CLI
2017-09-01 12:09:33 +05:30
Rudhra Raveendran fe3ec335fe made CLI cleaner 2017-08-29 10:33:01 -04:00
Chinmay Pandhare eafc639555 Merge pull request #102 from ROODAY/step-enhancements
Merging Step Enhancements!
2017-08-29 00:25:38 +05:30
Rudhra Raveendran 7da59aed0c Updated README to include CLI instructions 2017-08-28 14:38:26 -04:00
Rudhra Raveendran d759680869 abstracted validation to a function 2017-08-28 12:51:10 -04:00
Rudhra Raveendran 4c662d908e grammar 2017-08-28 12:16:34 -04:00
Rudhra Raveendran c5d62e5529 Resolved #97 and #100 2017-08-28 12:14:48 -04:00
Chinmay Pandhare d466afb90d Merge pull request #95 from ccpandhare/development2
Added Basic CLI
2017-08-26 03:46:14 +05:30
Chinmay Pandhare 72435eb9a4 Merge publiclab:master 2017-08-26 03:29:00 +05:30
Chinmay Pandhare c7e4ec7c4a Added outputs, fix modulesInfo 2017-08-26 03:27:10 +05:30
Chinmay Pandhare c28c958414 Merge pull request #94 from ccpandhare/master
Bugfixes
2017-08-24 20:19:03 +05:30
Chinmay Pandhare 38975791ca Basic CLI 2017-08-24 20:13:58 +05:30
Chinmay Pandhare dbc0ad6e69 Merge from master 2017-08-24 20:07:29 +05:30
Chinmay Pandhare c3c6713ec1 More Bugfixes 2017-08-24 20:05:57 +05:30
Chinmay Pandhare 873492a456 Binary output can be exported to any directory 2017-08-24 19:35:15 +05:30
Chinmay Pandhare ca044b1b01 Bugfixes 2017-08-24 19:07:22 +05:30
Chinmay Pandhare 0fa0684076 Merge pull request #92 from ccpandhare/demo
Added Image Sequencer Demo
2017-08-21 21:49:39 +05:30
Chinmay Pandhare c655b62813 Added output documentation in CONTRIBUTING.md 2017-08-21 10:51:08 +05:30
Chinmay Pandhare 69c656fbd5 Added comments in demo 2017-08-21 10:48:56 +05:30
Chinmay Pandhare ee467f2a7b Fix build 2017-08-21 10:34:50 +05:30
Chinmay Pandhare 21796bb58c Added output example in demo 2017-08-21 10:11:54 +05:30
Chinmay Pandhare cc308748c8 Add comments; Remove do-nothing, do-nothing-pix 2017-08-21 10:01:02 +05:30
Chinmay Pandhare 0095462384 Added webgl-distort to list of module candidates 2017-08-20 18:34:04 +05:30
Chinmay Pandhare 54c7393417 Update gitignore 2017-08-20 18:18:18 +05:30
Chinmay Pandhare 555ea9b945 Bootstrap 2017-08-20 18:13:09 +05:30
Chinmay Pandhare f879f31e4f Merge Demo 2017-08-20 14:28:39 +05:30
Chinmay Pandhare 782e60269e options accessible via step, implemented options in demo 2017-08-15 14:27:38 +05:30
Chinmay Pandhare 963129d513 Updated README and CONTRIBUTING 2017-08-15 08:12:12 +05:30
Chinmay Pandhare d207c147fe implemented modulesInfo in Demo 2017-08-15 08:02:54 +05:30
Chinmay Pandhare a9e0068628 modulesinfo browserify fix 2017-08-15 07:16:19 +05:30
Chinmay Pandhare 5b7e47dd79 modules info 2017-08-15 06:56:19 +05:30
Chinmay Pandhare 95ffa0453b Updated Demo 2017-08-14 09:23:33 +05:30
Chinmay Pandhare 76ffb6a013 Implemented addStep, removeStep, loadImage in Demo 2017-08-14 09:22:15 +05:30
Chinmay Pandhare 78fb43a8af Demo 2017-08-13 05:20:02 +05:30
Chinmay Pandhare 04e2ee7903 Added Single Image Demo Markup 2017-08-13 05:12:33 +05:30
Chinmay Pandhare 06af6007a2 Merge branch 'master' of https://github.com/publiclab/image-sequencer into demo 2017-08-13 04:33:25 +05:30
Chinmay Pandhare 42425bcf69 Merge pull request #89 from ccpandhare/testing
Image Diff based testing for PNG images
2017-08-12 09:21:37 +05:30
Chinmay Pandhare 3b73c6d700 Merge pull request #90 from ccpandhare/development2
Added exportBin method
2017-08-10 15:51:07 +05:30
Chinmay Pandhare 889c751399 Built for Browser 2017-08-10 01:03:29 +05:30
Chinmay Pandhare 26538891a3 Added exportBin method 2017-08-10 00:54:58 +05:30
Chinmay Pandhare 1510043a50 Remove image-diff and imagemagick from devDependancies 2017-08-08 15:39:23 +05:30
Chinmay Pandhare d34130b791 Merge from PublicLab/master 2017-08-08 15:35:07 +05:30
Chinmay Pandhare 8579cfafa7 Image Diff based testing 2017-08-08 15:34:12 +05:30
Chinmay Pandhare 2f117c1f35 imagemagick 2017-08-08 02:09:43 +05:30
Chinmay Pandhare 14b1152e5a Added Demo for FisheyeGl Module (#62)
* demo for FisheyeGl

* demo for FisheyeGl - Bugfixes

* Demo Created

* Demo Created

* Changed Title in Demo

* Improved Demo

* Organize examples directory, Recreate FisheyeGl Demo of fisheyegl using Image Sequencer UI

* Modified tests
2017-08-06 16:41:12 -04:00
Chinmay Pandhare c90bf3fa0a Merge pull request #88 from ccpandhare/master
Remove lurking tape-test file
2017-08-06 18:06:27 +05:30
Chinmay Pandhare 802648ae69 Remove lurking tape-test file 2017-08-06 17:56:50 +05:30
Chinmay Pandhare 812cd808c7 Made Requested Changes 2017-08-06 17:51:54 +05:30
Chinmay Pandhare 45328999f3 Merge pull request #72 from ccpandhare/modules2
Added DecodeQr Module, Test
2017-08-05 23:10:40 +05:30
Chinmay Pandhare 88f31c9665 Merge from publicab/master 2017-08-05 23:03:32 +05:30
Chinmay Pandhare 235b2428ee imagemagick 2017-08-05 23:00:13 +05:30
Chinmay Pandhare 3354071548 Merge Demo 2017-08-01 23:30:17 +05:30
Chinmay Pandhare 02ec74bad3 Modified tests 2017-08-01 23:24:50 +05:30
Chinmay Pandhare b8ec24c185 Organize examples directory, Recreate FisheyeGl Demo of fisheyegl using Image Sequencer UI 2017-08-01 23:19:36 +05:30
Chinmay Pandhare 187f66429a Merge Commit 2017-08-01 01:32:58 +05:30
Chinmay Pandhare 8f5e9f0c40 Merge pull request #61 from ccpandhare/modules
Added FisheyeGl Module
2017-07-29 22:30:30 +05:30
Chinmay Pandhare e6f1ff3243 Resolve Conflicts; Update DecodeQr Module 2017-07-29 04:25:50 +05:30
Chinmay Pandhare b9797a0329 Merge #86 from publiclab/image-sequencer 2017-07-29 04:13:04 +05:30
Chinmay Pandhare 61bd95355b Use 'fisheyegl' from npm 2017-07-29 04:08:28 +05:30
Chinmay Pandhare 9208216f99 Merge pull request #86 from ccpandhare/development3
Rework UI Handling
2017-07-29 03:56:24 +05:30
Chinmay Pandhare 3421714c2b Merge Commit 2017-07-29 03:50:31 +05:30
Chinmay Pandhare 93941b1280 Updated CONTRIBUTING.md 2017-07-29 03:33:35 +05:30
Chinmay Pandhare 6eea7b8bd6 Updated README.md 2017-07-29 03:31:33 +05:30
Chinmay Pandhare d1b47a73fe Updated README.md 2017-07-29 03:28:39 +05:30
Chinmay Pandhare 31e9b3ec9d Complete UI Handling 2017-07-29 03:20:17 +05:30
Chinmay Pandhare 1b7dadf41f Merge publiclab/master after #81 2017-07-29 02:58:51 +05:30
Chinmay Pandhare f1f3574468 Rework UI Handling 2017-07-29 02:53:39 +05:30
Chinmay Pandhare 8610ab1ff8 Merge pull request #81 from ccpandhare/development2
Support External Images
2017-07-29 02:52:50 +05:30
Chinmay Pandhare 2888c2d735 Patch for UI 2017-07-28 18:01:22 +05:30
Chinmay Pandhare 53a8963b72 Build Commit 2017-07-28 12:37:57 +05:30
Chinmay Pandhare 6e136e6c2f Updated README.md 2017-07-28 12:33:23 +05:30
Chinmay Pandhare 467fe1d2e5 Added Unit Test for URL Support 2017-07-28 12:32:29 +05:30
Chinmay Pandhare b59383ce53 Added URL support for Node.js client 2017-07-28 12:32:04 +05:30
Chinmay Pandhare b1c9949757 Updated README 2017-07-28 12:07:39 +05:30
Chinmay Pandhare f3f3f5f615 Merge commit 2017-07-28 12:01:29 +05:30
Chinmay Pandhare 7f17f825ab LoadImages will return nothing, only call a callback 2017-07-28 12:00:30 +05:30
Chinmay Pandhare ec4459309b Split README (#80)
* Split README

* Added 'Jump to' section in README
2017-07-27 20:26:48 +02:00
jywarren d80a587bf3 version bump 2017-07-27 14:20:32 -04:00
Chinmay Pandhare d7eb5a2f0c Update README, Merge Master 2017-07-27 23:44:14 +05:30
Chinmay Pandhare 03d9ff1ea2 Merge pull request #84 from ccpandhare/master
Update main script
2017-07-27 23:35:15 +05:30
Chinmay Pandhare 2e284560e2 Update main script 2017-07-27 23:21:40 +05:30
Chinmay Pandhare f5c21e7c82 Merge pull request #76 from ccpandhare/testing
Added tests to check support for PNG and GIF
2017-07-27 02:20:44 +05:30
Chinmay Pandhare fab35c5efb Merge Commit 2017-07-27 01:06:03 +05:30
Chinmay Pandhare 29cb36dd31 BuildFix 2017-07-26 23:26:18 +05:30
Chinmay Pandhare d90c61f85d Support External Images via Canvas 2017-07-26 23:16:32 +05:30
Chinmay Pandhare 21334ff5d8 Merge pull request #67 from ccpandhare/development
Implemented UI Methods
2017-07-25 02:36:15 +05:30
Chinmay Pandhare 1117279d44 Added a test to check support for PNG and GIF 2017-07-25 01:47:23 +05:30
Chinmay Pandhare 6baed92ec6 Build Completed 2017-07-25 01:11:36 +05:30
Chinmay Pandhare e9f045f5cd Merge master, Rename this_ and ui: 'none' 2017-07-25 00:59:58 +05:30
Chinmay Pandhare ec657a6735 Explained imageName in README.md 2017-07-25 00:45:54 +05:30
Chinmay Pandhare 246130c8e6 Merge pull request #73 from lpinca/gh-31
Remove base64-stream dependency and fix #31
2017-07-24 23:56:49 +05:30
Luigi Pinca 82bb274cfb Remove base64-stream dependency
Fixes: https://github.com/publiclab/image-sequencer/issues/31
2017-07-24 16:25:45 +02:00
Chinmay Pandhare 78beae48d9 Added DecodeQr Module, Test 2017-07-23 12:50:22 +05:30
Chinmay Pandhare 7584b1ec45 Added Docs, updated method names 2017-07-23 11:57:05 +05:30
Chinmay Pandhare cfdc4c509e BuildFix (BugFix) 2017-07-22 02:12:33 +05:30
Chinmay Pandhare 867792927e BuildFix (BugFix) 2017-07-22 02:12:14 +05:30
Chinmay Pandhare 555e37190b Implemented UI Methods 2017-07-22 02:01:03 +05:30
Chinmay Pandhare a74f67ef06 Built for Browser 2017-07-17 16:00:32 +05:30
Chinmay Pandhare b963dfd297 Improved Demo 2017-07-17 15:54:56 +05:30
Chinmay Pandhare 10dadeb63b Improved Demo 2017-07-17 15:53:31 +05:30
Chinmay Pandhare c4c1f5bd2d Improved Demo 2017-07-17 15:15:07 +05:30
Chinmay Pandhare 86d475d3e7 Removed Globals 2017-07-17 15:09:13 +05:30
Chinmay Pandhare 52e8e652c3 Bugfix 2017-07-17 12:07:33 +05:30
Chinmay Pandhare 73930849a7 Merge from Demo 2017-07-17 11:58:51 +05:30
Chinmay Pandhare dc9b9bcde3 Changed Title in Demo 2017-07-17 11:57:48 +05:30
Chinmay Pandhare dd595f371c Demo Created 2017-07-17 11:56:08 +05:30
Chinmay Pandhare 1404d455fc Merge from demo 2017-07-17 11:54:31 +05:30
Chinmay Pandhare 5994963253 Merge from demo 2017-07-17 11:54:09 +05:30
Chinmay Pandhare e6a96d723d Demo Created 2017-07-17 11:51:54 +05:30
Chinmay Pandhare 43d55d2781 demo for FisheyeGl - Bugfixes 2017-07-17 11:23:35 +05:30
Chinmay Pandhare 8d8cf33d23 demo for FisheyeGl 2017-07-16 17:32:35 +05:30
Chinmay Pandhare 09c0a180ec Added Documentation for FisheyeGl 2017-07-16 16:51:28 +05:30
Chinmay Pandhare 51e6c70fc8 Added FisheyeGl Module 2017-07-16 15:39:38 +05:30
Chinmay Pandhare 41c392d838 Merge Master 2017-07-11 12:44:09 +05:30
Chinmay Pandhare 368d11230c Merge ccpandhare:master 2017-07-09 23:48:06 +05:30
Chinmay Pandhare a4e7470a5e Merge Testing 2017-07-08 20:09:26 +05:30
Chinmay Pandhare 3112816afc Merge testing 2017-07-08 19:38:16 +05:30
Chinmay Pandhare 2eb41b20f9 Merge Commit 2017-07-08 19:13:12 +05:30
Chinmay Pandhare ec6cc396e4 Merge Commit 2017-07-08 19:12:15 +05:30
jywarren 10dc6c0a7c Merge branch 'master' into gh-pages 2017-03-04 11:22:11 -05:00
jywarren f6225422b1 Merge branch 'master' into gh-pages 2017-01-09 21:11:39 -05:00
jywarren 5dfbc9d28b Merge branch 'master' into gh-pages 2017-01-09 20:27:10 -05:00
jywarren 74eb686604 Merge branch 'master' into gh-pages 2017-01-09 18:06:50 -05:00
jywarren fedccd59a1 Merge branch 'master' into gh-pages 2017-01-06 01:39:26 -05:00
jywarren f1551abb7f .nojekyll file to get gh-pages to see node_modules 2017-01-02 23:59:34 -05:00
jywarren 7c1228fca9 .gitignore for gh-pages 2017-01-02 23:26:54 -05:00
jywarren 1bf58351b4 basic gh-pages deps 2017-01-02 23:19:02 -05:00
58 changed files with 6879 additions and 2229 deletions
+6
View File
@@ -25,6 +25,7 @@ build/Release
# Dependency directory
node_modules
node_modules/*
# Optional npm cache directory
.npm
@@ -34,3 +35,8 @@ node_modules
*.swp
todo.txt
test.js
output.txt
output/
node_modules/
node_modules/*
View File
+296
View File
@@ -0,0 +1,296 @@
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.
## Contributing modules
Any module must look like this :
```js
module.exports = function ModuleName(options,UI) {
options = options || {};
options.title = "Title of the Module";
UI.onSetup(options.step);
var output;
function draw(input,callback) {
UI.onDraw(options.step);
var output = /*do something with the input*/ ;
this.output = output;
options.step.output = output.src;
callback();
UI.onComplete(options.step);
}
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()
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
### UI Methods
The module is responsible to emit 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.
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:
```js
input = {
src: "datauri here",
format: "jpeg/png/etc"
}
```
## options.title
For display in the web-based UI, each module may also have a title `options.title`.
## Info file
All module folders must have an `info.json` file which looks like the following:
```json
{
"name": "Name of Module to be displayed",
"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.
### Module example
See existing module `green-channel` for an example: https://github.com/publiclab/image-sequencer/tree/master/src/modules/GreenChannel/Module.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
* [x] 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
* https://github.com/jywarren/webgl-distort/ - an image distortion module
## 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
},
'colors': {
type: 'select',
label: 'Colors',
options: [
{ name: '0', value: '0', default: true },
{ name: '1', value: '1' },
{ name: '2', value: '2' }
]
}
}
```
Possible web-based commandline interface: https://hyper.is/?
### Path cutting
* threshold
* vectorize
* edge detect
* direction find (vectorize and colorize)
+108 -193
View File
@@ -26,6 +26,14 @@ It is also for prototyping some other related ideas:
* [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)
## Jump to:
* [Quick Usage](#quick-usage)
* [Classic Usage](#classic-usage)
* [Method Chaining](#method-chaining)
* [Multiple Images](#multiple-images)
* [Creating a User Interface](#creating-a-user-interface)
## Quick Usage
Image Sequencer can be used to run modules on an HTML Image Element using the
@@ -49,6 +57,24 @@ 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)
-o | --output [PATH] | Directory where output will be stored. (optional)
-op | --opions {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 ..."
## Classic Usage
@@ -68,11 +94,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 +197,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 +206,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 +253,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 +369,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.
+5011 -1671
View File
File diff suppressed because one or more lines are too long
+21
View 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)
+96
View File
@@ -0,0 +1,96 @@
/* 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;
}
#imageSelect {
padding: 30px;
margin: 0 20% 30px;
border: 4px dashed #ccc;
border-radius: 8px;
text-align: center;
color: #444;
}
#imageSelect 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;
}
#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;
}
+136
View File
@@ -0,0 +1,136 @@
<!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="../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>
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

File diff suppressed because one or more lines are too long
+221
View File
@@ -0,0 +1,221 @@
<!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">
<title>Image Sequencer</title>
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="demo.css">
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
</head>
<body>
<div class="container-fluid">
<header class="text-center">
<h1>Image Sequencer</h1>
<h3>
<a href="https://github.com/publiclab/image-sequencer"><i class="fa fa-github"></i></a>
</h3>
</header>
<div id="imageSelect">
<p><i>Select an image to start with!</i></p>
<center><input type="file" id="file" value=""></center>
</div>
<section id="steps" class="row"></section>
<section id="addStep">
<div class="row">
<select class="form-control">
<option value="none" disabled selected>Select a new step...</option>
</select>
</div>
<hr />
<div class="row">
<div id="options"></div>
</div>
<div class="row add">
<button class="btn btn-success btn-lg" name="add">Add Step</button>
</div>
</section>
</div>
<script type="text/javascript">
// Initialize Image Sequencer
var sequencer = ImageSequencer();
window.onload = function() {
// 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>'
);
}
// Initial definitions
var steps = document.querySelector('#steps');
var parser = new DOMParser();
var reader = new FileReader();
initial = "images/grid.png";
// Set the UI in sequencer. This Will generate HTML based on
// Image Sequencer events :
// onSetup : Called every time a step is added
// onDraw : Called every time a step starts draw
// onComplete : Called every time a step finishes drawing
// onRemove : Called everytime a step is removed
// The variable 'step' stores useful data like input and
// output values, step information.
// See documetation for more details.
sequencer.setUI({
onSetup: function(step) {
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">\
<img alt="" class="img-thumbnail"/>\
</div>\
</div>\
';
var tools =
'<div class="tools btn-group">\
<button confirm="Are you sure?" class="remove btn btn-xs btn-default">\
<i class="fa fa-trash"></i>\
</button>\
</div>';
step.ui = parser.parseFromString(step.ui,'text/html');
step.ui = step.ui.querySelector('div.row');
step.imgElement = step.ui.querySelector('img');
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = sequencer.modulesInfo(step.name).inputs;
var outputs = sequencer.modulesInfo(step.name).outputs;
var io = Object.assign(inputs,outputs);
for (var i in io) {
var div = document.createElement('div');
div.className = "row";
div.setAttribute('name',i);
div.innerHTML = "<div class='det'>" + i + ": " + "<span></span></div>";
step.ui.querySelector('div.details').appendChild(div);
}
}
if(step.name != "load-image")
step.ui.querySelector('div.details').appendChild(
parser.parseFromString(tools,'text/html')
.querySelector('div')
);
steps.appendChild(step.ui);
},
onComplete: function(step) {
step.imgElement.src = step.output;
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = sequencer.modulesInfo(step.name).inputs;
var outputs = sequencer.modulesInfo(step.name).outputs;
for (var i in inputs) {
step.ui.querySelector('div[name="'+i+'"] span')
.innerHTML = step.options[i];
}
for (var i in outputs) {
step.ui.querySelector('div[name="'+i+'"] span')
.innerHTML = step[i];
}
}
},
onRemove: function(step) {
step.ui.remove();
}
});
sequencer.loadImage('images/grid.png',function(){
this.addSteps('invert').run();
});
$('#addStep select').on('change', function(){
$('#options').html('');
var m = $('#addStep select').val();
for(var input in modulesInfo[m].inputs) {
$('#options').append(
'<div class="row">\
<div class="col-md-5 labels">\
'+input+':\
</div>\
<div class="col-md-5">\
<input class="form-control" type="text" name="'+input+'" value="" placeholder="'+
modulesInfo[m].inputs[input].default+'"/>\
</div>\
</div>'
);
}
});
$('#addStep button').on('click',function(){
var options = {};
var inputs = $('#options input');
$.each(inputs, function() {
options[this.name] = $(this).val();
});
if($('#addStep select').val()=="none") return;
sequencer.addSteps($('#addStep select').val(),options).run();
});
$('body').on('click','button.remove',function(){
var index = $('button.remove').index(this) + 1;
sequencer.removeSteps(index).run();
});
$('#file').on('change',function(e){
var file = e.target.files[0];
if(!file) return;
reader.onload = function(){
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);
});
}
</script>
</body>
</html>
+2 -2
View File
@@ -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>
+3 -79
View File
@@ -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>
Regular → Executable
+109 -9
View File
@@ -1,11 +1,111 @@
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 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('-op, --opions {object}', 'Options for the step')
.parse(process.argv);
// Parse step into an array to allow for multiple steps.
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(){
// 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 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);
});
sequencer.run();
}});
// Run the sequencer.
sequencer.run(function(){
// Export all images as binary files.
sequencer.exportBin(program.output);
console.log("Files will be exported to \""+program.output+"\"");
});
});
// Takes an array of steps and checks if they are valid steps for the sequencer.
function validateSteps(steps) {
// Assume all are valid in the beginning.
var valid = true;
steps.forEach(function(step) {
// 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;
}
+12 -4
View File
@@ -1,8 +1,8 @@
{
"name": "image-sequencer",
"version": "0.0.1",
"version": "0.1.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\""
},
@@ -21,14 +21,18 @@
},
"dependencies": {
"bootstrap": "~3.2.0",
"data-uri-to-buffer": "^2.0.0",
"fisheyegl": "^0.1.2",
"font-awesome": "~4.5.0",
"jquery": "~2",
"jsqr": "^0.2.2",
"readline-sync": "^1.4.7",
"urify": "^2.1.0"
},
"devDependencies": {
"base64-stream": "~0.1.3",
"browserify": "13.0.0",
"buffer": "~5.0.2",
"data-uri-to-buffer": "^2.0.0",
"get-pixels": "~3.3.0",
"grunt": "^0.4.5",
"grunt-browserify": "^5.0.0",
@@ -36,6 +40,7 @@
"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",
@@ -43,5 +48,8 @@
"tape": ">=4.7.0",
"tape-run": "^3.0.0"
},
"homepage": "https://github.com/publiclab/image-sequencer"
"homepage": "https://github.com/publiclab/image-sequencer",
"bin": {
"sequencer": "./index.js"
}
}
+12 -16
View File
@@ -1,30 +1,26 @@
function AddStep(ref, image, name, o) {
function addStep(image, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
var o = ref.copy(o_);
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
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;
o.inBrowser = ref.options.inBrowser;
var module = ref.modules[name].call(ref.images,o);
o.step = {
name: o.name,
ID: o.number,
imageName: o.image,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui,
options: o
};
var UI = ref.events;
var module = ref.modules[name][0](o,UI);
ref.images[image].steps.push(module);
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;
}
+54
View File
@@ -0,0 +1,54 @@
var fs = require('fs');
var getDirectories = function(rootDir, cb) {
fs.readdir(rootDir, function(err, files) {
var dirs = [];
if(typeof(files)=="undefined") {
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) {
dir = (dir[dir.length-1]=="/") ? dir : dir + "/";
if(ref.options.inBrowser) return false;
fs.access(dir, function(err){
if(err) fs.mkdir(dir, function() {});
});
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;
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(){
});
}
}
})
});
}
+58 -16
View File
@@ -41,14 +41,16 @@ ImageSequencer = function ImageSequencer(options) {
modules = require('./Modules'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [];
inputlog = [],
events = require('./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]));}
@@ -66,7 +68,8 @@ ImageSequencer = function ImageSequencer(options) {
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
@@ -74,7 +77,7 @@ ImageSequencer = function ImageSequencer(options) {
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]));
@@ -93,7 +96,7 @@ ImageSequencer = function ImageSequencer(options) {
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]);
@@ -112,8 +115,7 @@ ImageSequencer = function ImageSequencer(options) {
}
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]));
@@ -131,25 +133,38 @@ ImageSequencer = function ImageSequencer(options) {
function loadImages() {
var args = [];
var sequencer = this;
for (var arg in arguments) args.push(copy(arguments[arg]));
var json_q = formatInput.call(this,args,"l");
inputlog.push({method:"loadImages", json_q:copy(json_q)});
var loadedimages = this.copy(json_q.loadedimages);
// require('./LoadImage')(this,i,json_q.images[i]);
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('./LoadImage')(sequencer,img,json_q.images[img],function(){
load(++i);
});
}
load(0);
}
function replaceImage(selector,steps,options) {
@@ -157,9 +172,35 @@ ImageSequencer = function ImageSequencer(options) {
return require('./ReplaceImage')(this,selector,steps);
}
function setUI(UI) {
this.events = require('./UserInterface')(UI);
}
var exportBin = function(dir) {
return require('./ExportBin')(dir,this);
}
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,10 +208,11 @@ 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
+15 -20
View File
@@ -1,31 +1,26 @@
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,
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;
}
+68 -7
View File
@@ -1,38 +1,99 @@
function LoadImage(ref, name, src) {
function CImage(src) {
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
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",
ID: ref.options.sequencerCounter++,
imageName: name,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var image = {
src: src,
steps: [{
options: {
id: ref.options.sequencerCounter++,
id: step.ID,
name: "load-image",
title: "Load Image"
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;
},
output: CImage(src)
}]
};
ref.images[name] = image;
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);
+22 -8
View File
@@ -1,12 +1,26 @@
/*
* 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')
'green-channel': [
require('./modules/GreenChannel/Module'),require('./modules/GreenChannel/info')
],
'ndvi-red': [
require('./modules/NdviRed/Module'),require('./modules/NdviRed/info')
],
'invert': [
require('./modules/Invert/Module'),require('./modules/Invert/info')
],
'crop': [
require('./modules/Crop/Module'),require('./modules/Crop/info')
],
'segmented-colormap': [
require('./modules/SegmentedColormap/Module'),require('./modules/SegmentedColormap/info')
],
'decode-qr': [
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
],
'fisheye-gl': [
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
]
}
+53 -28
View File
@@ -1,41 +1,66 @@
/*
* Default UI for each image-sequencer module
* User Interface Handling 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);
module.exports = function UserInterface(events = {}) {
// method to remove the UI for a given method, and remove the step
function display(image) {
options.el.find('.image').html(image);
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+"\".");
}
}
// method to remove the UI for a given method, and remove the step
function remove() {
$('div#sequencer-'+options.id).remove();
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+"\".");
}
}
// 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>');
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+"\".");
}
}
return {
el: options.el,
uniqueSelector: options.uniqueSelector,
selector: options.selector,
display: display,
remove: remove
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;
}
+25 -16
View File
@@ -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);
});
});
}
};
+25 -8
View File
@@ -13,30 +13,47 @@
* y = options.y
* y = options.y + options.h
*/
module.exports = function CropModule(options) {
module.exports = function CropModule(options,UI) {
options = options || {};
options.title = "Crop Image";
var this_ = this;
var output
// Tell the UI that a step has been added
UI.onSetup(options.step);
var output;
// This function is caled everytime the step has to be redrawn
function draw(input,callback) {
const this_ = this;
// Tell the UI that the step has been triggered
UI.onDraw(options.step);
var step = this;
require('./Crop')(input,options,function(out,format){
this_.output = {
// This output is accessible to Image Sequencer
step.output = {
src: out,
format: format
}
callback();
});
// This output is accessible to the UI
options.step.output = out;
// Tell the UI that the step has been drawn
UI.onComplete(options.step);
// Tell Image Sequencer that step has been drawn
callback();
});
}
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"name": "Crop",
"description": "Crop image to given x, y, w, h",
"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%)"
}
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
* Decodes QR from a given image.
*/
module.exports = function DoNothing(options,UI) {
options = options || {};
options.title = "Decode QR Code";
// Tell the UI that a step has been added
UI.onSetup(options.step);
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
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Decode QR",
"inputs": {
},
"outputs": {
"qrval": {
"type": "text"
}
}
}
-20
View File
@@ -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
}
}
-32
View File
@@ -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
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* Resolves Fisheye Effect
*/
module.exports = function DoNothing(options,UI) {
options = options || {};
options.title = "Fisheye GL";
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
}
}
+64
View File
@@ -0,0 +1,64 @@
{
"name": "Fisheye GL",
"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)"
}
}
}
+23 -6
View File
@@ -1,23 +1,38 @@
/*
* Display only the green channel
*/
module.exports = function GreenChannel(options) {
module.exports = function GreenChannel(options,UI) {
options = options || {};
options.title = "Green channel only";
options.description = "Displays only the green channel of an image";
// Tell UI that a step has been set up
UI.onSetup(options.step);
var output;
//function setup() {} // optional
function draw(input,callback) {
var this_ = this;
// Tell UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
return [0, g, 0, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format: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,
@@ -25,12 +40,14 @@ module.exports = function GreenChannel(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
output: output,
UI: UI
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "Green Channel",
"inputs": {
}
}
+25 -7
View File
@@ -1,23 +1,40 @@
/*
* Display only the green channel
*/
module.exports = function GreenChannel(options) {
module.exports = function GreenChannel(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) {
var this_ = this;
// Tell UI that a step is being drawn.
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
return [255-r, 255-g, 255-b, a];
}
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,
@@ -25,12 +42,13 @@ module.exports = function GreenChannel(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
output: output,
UI: UI
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "Invert",
"inputs": {
}
}
+25 -4
View File
@@ -1,22 +1,40 @@
/*
* NDVI with red filter (blue channel is infrared)
*/
module.exports = function NdviRed(options) {
module.exports = function NdviRed(options,UI) {
options = options || {};
options.title = "NDVI for red-filtered cameras (blue is infrared)";
// 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) {
var this_ = this;
// Tell the UI that a step is being drawn.
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
var ndvi = (b - r) / (1.00 * b + r);
var x = 255 * (ndvi + 1) / 2;
return [x, x, x, 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 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,
@@ -24,10 +42,13 @@ module.exports = function NdviRed(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw
draw: draw,
output: output,
UI:UI
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "NDVI Red",
"inputs": {
}
}
+24 -4
View File
@@ -1,19 +1,37 @@
module.exports = function SegmentedColormap(options) {
module.exports = function SegmentedColormap(options,UI) {
options = options || {};
options.title = "Segmented Colormap";
// Tell the UI that a step has been set up.
UI.onSetup(options.step);
var output;
// This function is called on every draw.
function draw(input,callback) {
var this_ = this;
// Tell the UI that the step is being drawn
UI.onDraw(options.step);
var step = 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}
// 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,
@@ -22,11 +40,13 @@ module.exports = function SegmentedColormap(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "Segmented Colormap",
"inputs": {
"colormap": {
"type": "select",
"desc": "Name of the Colormap",
"default": "default",
"values": ["default","greyscale","stretched","fastie"]
}
}
}
View File
+19 -17
View File
@@ -7,16 +7,15 @@ module.exports = function PixelManipulation(image, options) {
options = options || {};
options.changePixel = options.changePixel || function changePixel(r, g, b, a) {
return [r, g, b, a];
}
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');
return;
}
// iterate through pixels;
@@ -25,7 +24,7 @@ module.exports = function PixelManipulation(image, options) {
for(var x = 0; x < pixels.shape[0]; x++) {
for(var y = 0; y < pixels.shape[1]; y++) {
pixel = options.changePixel(
var pixel = options.changePixel(
pixels.get(x, y, 0),
pixels.get(x, y, 1),
pixels.get(x, y, 2),
@@ -40,19 +39,22 @@ module.exports = function PixelManipulation(image, options) {
}
}
options.format = "jpeg";
// 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();
});
});
}
};
+36 -25
View File
@@ -8,7 +8,7 @@ var test = require('tape');
require('../src/ImageSequencer.js');
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('loadImages/loadImage has a name generator.', function (t){
@@ -17,22 +17,27 @@ test('loadImages/loadImage has a name generator.', function (t){
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('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){
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();
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){
@@ -44,11 +49,14 @@ test('addSteps is two-way chainable without loadImages.', function (t){
});
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();
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){
@@ -59,12 +67,15 @@ test('removeSteps is two-way chainable without loadImages.', function (t){
});
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();
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){
+64 -8
View File
@@ -1,25 +1,81 @@
'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');
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);
//require image files as DataURLs so they can be tested alike on browser and Node.
var sequencer = ImageSequencer({ ui: false });
sequencer.addSteps(['do-nothing-pix','invert','invert']);
sequencer.run();
var qr = require('./images/IS-QR.js');
var test_png = require('./images/test.png.js');
var test_gif = require('./images/test.gif.js');
sequencer.loadImages(test_png);
sequencer.addSteps(['invert','invert']);
test("Preload", function(t) {
sequencer.run(function(){
t.end();
});
});
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();
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) {
t.equal(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[3].output.src);
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(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(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(function(out){
t.equal(1,1)
t.end();
});
});
});
+41 -29
View File
@@ -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,30 @@ 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);
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 +73,38 @@ test('loadImage works too.', function (t){
});
test('addSteps("image","name") adds a step', function (t) {
sequencer.addSteps('test','do-nothing');
sequencer.addSteps('test','green-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, "green-channel", "Correct Step Added");
t.end();
});
test('addSteps("name") adds a step', function (t) {
sequencer.addSteps('do-nothing');
sequencer.addSteps('green-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, "green-channel", "Correct Step Added");
t.end();
});
test('addSteps(["name"]) adds a step', function (t) {
sequencer.addSteps(['do-nothing','do-nothing-pix']);
sequencer.addSteps(['green-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, "green-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('green-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, "green-channel", "Correct Step Added");
t.end();
});
test('addSteps("image","name",o) adds a step', function (t) {
sequencer.addSteps('test','do-nothing',{});
sequencer.addSteps('test','green-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, "green-channel", "Correct Step Added");
t.end();
});
@@ -115,30 +127,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,'green-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, "green-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,'green-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, "green-channel", "Correct Step Inserted");
t.end();
});
test('insertSteps(position,"module") inserts a step', function (t) {
sequencer.insertSteps(1,'do-nothing');
sequencer.insertSteps(1,'green-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, "green-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:'green-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, "green-channel", "Correct Step Inserted");
t.end();
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long