Compare commits

...

192 Commits

Author SHA1 Message Date
Jeffrey Warren
991e9bb29c start to average module (#249)
* start to average module

* completed average module

* added average module, version bump
2018-05-09 09:39:33 -04:00
Jeffrey Warren
cbbfbff4bc bugfix version bump (#248) 2018-05-07 10:46:38 -04:00
Jeffrey Warren
768e2ce95d Ui refactor and drag to crop (#245)
* Drag to crop rebase step (#244)

* 1.1.0

* 1.2.0

* added plugin

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* drag to crop enabled

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fix

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* improvement

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* done dragToCrop

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* done with updated ui

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* solved bug for multiple consecutive crops

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fixed and updated

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* externalized image area select code

* major refactoring of crop drag and demo ui modules

* work on crop module and ui refactor

* completed Step drag ui refactor

* revert unbuilt to rebase

* built and version bump

* various fixes
2018-05-04 14:27:48 -04:00
Jeffrey Warren
51524f747a uses jquery if available in replace (#240) 2018-05-03 18:02:25 -04:00
Jeffrey Warren
b16b45afa8 removed jquery (#239)
* removed jquery

* version bump
2018-05-03 17:33:01 -04:00
Jeffrey Warren
39418da187 Format detection fix (#238)
* better format detection

* version
2018-05-03 16:35:02 -04:00
Jeffrey Warren
8547c60873 Update PixelManipulation.js (#236) 2018-05-02 17:09:09 -04:00
Jeffrey Warren
adc21ae843 Rename colormap (#235) 2018-05-02 16:08:50 -04:00
Jeffrey Warren
2abf0fae09 Code cleanup, re-organizing and tidying (#226)
* initial cleanup

* build

* additions and build
2018-04-29 12:16:11 -04:00
Jeffrey Warren
711ae62ee3 simplified demo ui (#224)
* simplified demo ui

* show description

* smaller, labelled example image
2018-04-28 18:40:34 -04:00
Jeffrey Warren
4d75fb9640 Infos updated for new title/description format (#221)
* initial work

* working with failing test

* info modules

* compiled

* rework of CONTRIBUTING
2018-04-24 17:15:27 -04:00
Marzanna
5ac3ef008d demo images can be downloaded by clicking (#220)
* demo images can be downloaded by clicking

* modified download to extract image type from url

* refactored image output into standalone function

* updated to call fileExtension function
2018-04-20 19:56:32 -04:00
Varun Gupta
98f913b32e Material icon (#217)
* add pwa icon

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* icon working

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-04-20 14:07:52 -04:00
Jeffrey Warren
c2756ffdbb Module info.json -based demo UI (WIP) (#219)
* initial work

* working with failing test

* now should pass test
2018-04-20 09:34:08 -04:00
Varun Gupta
d887f5eb61 Update version (#208)
* 1.1.0

* 1.2.0

* check in dist

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-04-09 16:53:40 -04:00
Varun Gupta
3b791ad58b add loading spinner fallback (#202)
* add loading spinner fallback fixes #189

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* handle testing with progress spinners

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* apply fixes and improvements

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add example

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* bug fixes

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* resolves #189

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fix

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-03-31 01:21:28 -04:00
Varun Gupta
fba80bb151 Dynamic fix as fun (#207)
getNeighborPixel(x, y) method for Dynamic module
2018-03-30 17:41:30 -04:00
Varun Gupta
0ceb36ffde 1.1.0 (#206) 2018-03-24 10:06:50 -04:00
Jeffrey Warren
edaa8895c7 Update info.json with NDVI description (#199)
* Update info.json

* compiled
2018-03-12 13:13:43 -04:00
Raounak Sharma
efd5bf7624 Changed the warning log (#195)
Since our execution is not exactly async, hence the corresponding change in
the log message is more suitable log.

close 194
2018-03-09 17:14:03 -05:00
Jeffrey Warren
96143564fa Update index.html 2018-03-01 13:39:29 -06:00
Gayathri
973ec6d6e4 Added how to run the debug script (#192) 2018-02-26 10:53:39 -05:00
Danielle Shwed
801ea35393 Add Saturation Module (#193)
* fixed blur module as well as issue with no output when running debug

* added saturation module

* fixing requested changes

* fixing exportBin

* requested fixes
2018-02-26 10:52:25 -05:00
Varun Gupta
e8ab480687 Added native gaussian Blurring module(no dependencies) (#186)
* Added Blur Module

* Issue 177 fixing Blur module

* blur module fixed info json

* fixing blur module

* Delete .DS_Store

* add a native blurring solution

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fixed package.json

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* delete .ds_store and update gitignore

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-22 16:43:14 -05:00
Varun Gupta
9d3e511bca resolves #184 (#185) 2018-02-21 22:10:25 -05:00
mayank1010
d3a4c336a0 Empty directory bug fixed (#179)
added the check to test if the directory is empty by comparing it's length with 0.
2018-02-21 14:55:24 -05:00
Varun Gupta
0fd797ed5a resolves #182 (#183)
Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-21 11:31:20 -05:00
Varun Gupta
27ee18b3d2 resolves #153 (#180)
Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-20 17:28:19 -05:00
Varun Gupta
5922b0de58 resolves #174 (#175)
Update readme -- resolves #174
2018-02-16 18:36:21 -05:00
Varun Gupta
fd45d3caf3 resolves #132 fix options object (#171)
* resolves #132 fix options object

* Add Documentation for --details parameter

* fix spacing issue

* update naming of options parameter

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-16 18:03:39 -05:00
Varun Gupta
bbef4bca57 Added Edge Detection module (#168)
* step 1 grayscale

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add basic edge detection resolves #166

* updated the description

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fixed typo

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* updated the description for edge detection module

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add NonMaxSuppression and DoubleThreshold

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* comlpeted edge detection

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* cleared remaining weak edges

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-16 17:07:00 -05:00
Varun Gupta
32c5a29906 resolves #131 create output directory immediately (#167)
* resolves #131 create output directory immediately

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fix error if no steps are passed

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add exit message if steps not passed

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add test for creating output directory
2018-02-06 16:42:22 -05:00
Varun Gupta
c5dee8aef3 Add bookmarklet (#164)
* resolves #162

* add bookmarklet link to readme resolves #148

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* Update README.md
2018-02-02 12:52:08 -05:00
Varun Gupta
071aa68de7 resolves #162 (#163)
remove module count test - resolves #162
2018-02-01 13:37:58 -05:00
Varun Gupta
87fa166595 Only brightness module (#161)
* fixed package.json

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* resolves #113 add brightness module

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-01 12:27:37 -05:00
Varun Gupta
a5d5dd9f52 fixed package.json (#160)
Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-02-01 11:55:01 -05:00
Rishabh Chaudhary
38accebcc3 Fixed - move module tests into separate test files #141 (#155)
* Fixed -  move module tests into separate test files #141

* Modification

* Restructure, fixed tests
2018-01-29 13:18:15 -05:00
Varun Gupta
c19a663fa2 resolves #133 add -b --basic mode to only output final image update readme (#154)
* resolves #133 add -b --basic mode to only output final image update readme

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* format logs and help

* update dist files
2018-01-23 12:26:02 -05:00
Varun Gupta
38cf05bb9e resolves #129 message to user informing about the async execution (#152)
Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
2018-01-21 10:26:37 -05:00
Chinmay Pandhare
3f99e2b44c fixed replaceImage (#151)
* 1.0.0

* Fixed Replace Image

* Added test to check if replaceImage works
2017-10-26 14:35:23 -04:00
Jeffrey Warren
73e5d14569 various tweaks and adjustments to demo; bugfix for select/input changes (#149) 2017-10-23 23:16:22 -04:00
Chinmay Pandhare
135c142ba8 Updates to Demo (#143)
* 1.0.0

* Added 'select' input

* Added loader

* Fix File Reader
2017-10-18 17:02:42 -04:00
Jeffrey Warren
14c59be19c Additions to README - diagrams (#144)
* Additions to README

* Update README.md

* Add files via upload
2017-10-18 16:32:33 -04:00
Jeffrey Warren
ec10ab0b0d Update imageSelection.js 2017-10-18 16:28:23 -04:00
Jeffrey Warren
1c7f59cbfd Dynamic module, new demo image, demo cleanup, re-configurable demo steps (#139)
* rebase and build

* bump version to v0.1.1
2017-10-13 21:30:06 -04:00
Jeffrey Warren
bef53ef1a2 Step descriptions (work in progress) (#135)
* additional changes, not sure of yet

* working, but probably redundant code

* remove comment line

* rebuild
2017-10-13 13:16:07 -04:00
Jeffrey Warren
1c7f7c15af assortment of tweaks, additional tests (#134) 2017-10-05 15:16:25 -06:00
makeupsomething
7cbf793655 Added uglify command to build function (#124) 2017-10-05 14:29:46 -06:00
Jeffrey Warren
829e19e58d various fixes and changes to SegmentedColormap module (#128) 2017-10-04 14:31:43 -04:00
Jeffrey Warren
ca6910017d url hash initial implementation (#125)
* url hash initial implementation

* completed url hash UI
2017-10-02 08:37:10 -04:00
Jeffrey Warren
4e780f3a91 drag drop added to demo (#121) 2017-09-11 20:30:50 -04:00
Jeffrey Warren
3a4e4f9575 Merge pull request #117 from publiclab/readme-fixes
Update README.md with fixed Contributing links
2017-09-10 13:11:25 -04:00
Jeffrey Warren
6420a70aad Update CONTRIBUTING.md 2017-09-10 13:11:07 -04:00
Jeffrey Warren
99ec91b916 Update README.md 2017-09-10 12:54:03 -04:00
Chinmay Pandhare
4d9a4bcaa1 Merge pull request #107 from ccpandhare/docs
Update Docs
2017-09-10 03:26:25 +05:30
Jeffrey Warren
0e50a1b2f5 Merge pull request #111 from publiclab/demo-css
Update demo.css for narrower screens
2017-09-09 09:30:33 -04:00
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
bde6dbed8f Cleanup CONTRIBUTING.md 2017-09-03 15:58:07 +05:30
Chinmay Pandhare
4fb1ecce86 Added Installation Instructions; Update Dependancies 2017-09-03 15:38:03 +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
103 changed files with 28619 additions and 12705 deletions

8
.gitignore vendored
View File

@@ -25,6 +25,7 @@ build/Release
# Dependency directory
node_modules
node_modules/*
# Optional npm cache directory
.npm
@@ -32,5 +33,12 @@ node_modules
# Optional REPL history
.node_repl_history
.DS_Store
*.swp
todo.txt
test.js
output.txt
output/
node_modules/
node_modules/*

0
.nojekyll Normal file
View File

219
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,219 @@
Contributing to Image Sequencer
===
Happily accepting pull requests; to edit the core library, modify files in `./src/`. To build, run `npm install` followed by `grunt build`.
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
## Jump To
* [README.md](https://github.com/publiclab/image-sequencer)
* [Contributing Modules](#contributing-modules)
* [Info File](#info-file)
* [Ideas](#ideas)
****
## Contribution ideas
See [this issue](https://github.com/publiclab/image-sequencer/issues/118) for a range of ideas for new contributions, and links to possibly helpful libraries. Also see the [new features issues list](https://github.com/publiclab/image-sequencer/labels/new-feature).
### Bugs
If you find a bug please list it here, and help us develop Image Sequencer by [opening an issue](https://github.com/publiclab/image-sequencer/issues/new)!
****
## Contributing modules
Most contributions can happen in modules, rather than to core library code. Modules and their [corresponding info files](#info-file) are included into the library in this file: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
Module names, descriptions, and parameters are set in the `info.json` file -- [see below](#info-file).
Any module must follow this basic format:
```js
module.exports = function ModuleName(options,UI) {
options = options || {};
UI.onSetup(options.step);
var output;
function draw(input,callback) {
UI.onDraw(options.step); // tell the UI to "draw"
var output = function(input){
/* do something with the input */
return input;
}
this.output = output(input); // run the output and assign it to this.output
options.step.output = output.src;
callback();
UI.onComplete(options.step); // tell UI we are done
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}
```
### options
The object `options` stores some important information. This is how you can accept
input from users. If you require a variable "x" from the user and the user passes
it in, you will be able to access it as `options.x`.
Options also has some in-built properties. The `options.inBrowser` boolean denotes
whether your module is being run on a browser.
### draw()
To add a module to Image Sequencer, it must have a `draw` method; you can wrap an existing module to add them:
* `module.draw(input, callback)`
The `draw` method should accept an `input` parameter, which will be an object of the form:
```js
input = {
src: "datauri of an image here",
format: "jpeg/png/etc"
}
```
The draw method is run every time the step is `run` using `sequencer.run()`.
So any calculations must go **into** the `draw()` method's definition.
What is not in the draw method, but is in the `module.exports` is executed only
when the step is added. So whatever external npm modules are to be loaded, or
constant definitions must be done **outside** the `draw()` method's definition.
`draw()` receives two arguments - `input` and `callback` :
* `input` is an object which is essentially the output of the previous step.
```js
input = {
src: "<$DataURL>",
format: "<png|jpeg|gif>"
}
```
* `callback` is a function which is responsible to tell the sequencer that the step has been "drawn".
When you have done your calculations and produced an image output, you are required to set `this.output` to an object similar to what the input object was, call `callback()`, and set `options.step.output` equal to the output DataURL
* `progressObj` is an optional additional Object that can be passed in the format `draw(input, callback, progressObj)`, which handles the progress output; see [Progress reporting](#progress-reporting) below.
### UI Methods
The module is responsible for emitting various events for the UI to capture.
There are four events in all:
* `UI.onSetup(options.step)` must be emitted when the module is added. So it must be emitted outside the draw method's definition as shown above.
* `UI.onDraw(options.step)` must be emitted whenever the `draw()` method is called. So it should ideally be the first line of the definition of the `draw` method.
* `UI.onComplete(options.step)` must be emitted whenever the output of a draw call
is ready. An argument, that is the DataURL of the output image must be passed in.
* `UI.onRemove(options.step)` is emitted automatically and the module should not emit it.
### Name and description
For display in the web-based demo UI, set the `name` and `description` fields in the `info.json` file for the module.
## Info file
All module folders must have an `info.json` file which looks like the following:
```json
{
"name": "Name of Module to be displayed",
"description": "Optional longer text explanation of the module's function",
"url": "Optional link to module's source code or documentation",
"inputs": {
"var1": {
"type": "text",
"default": "default value"
}
},
"outputs": {
"out1": {
"type": "text"
}
}
}
```
Types may be one of "text", "integer", "float", "select".
Integer and Float types should also specify minimum and maximum values like this:
```json
"var1": {
"type": "integer",
"min": 0,
"max": 4,
"default": 1
}
```
Similarly, "Select" type inputs should have a `values` array.
Also, A module may have output values. These must be defined as shown above.
### Progress reporting
The default "loading spinner" can be optionally overriden with a custom progress object to draw progress on the CLI, following is a basic module format for the same:
```js
module.exports = function ModuleName(options,UI) {
options = options || {};
UI.onSetup(options.step);
var output;
function draw(input,callback,progressObj) {
/* If you wish to supply your own progress bar you need to override progressObj */
progressObj.stop() // Stop the current progress spinner
progressObj.overrideFlag = true; // Tell image sequencer that you will supply your own progressBar
/* Override the object and give your own progress Bar */
progressObj = /* Your own progress Object */
UI.onDraw(options.step);
var output = function(input){
/* do something with the input */
return input;
};
this.output = output();
options.step.output = output.src;
callback();
UI.onComplete(options.step);
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}
```
The `progressObj` parameter of `draw()` is not consumed unless a custom progress bar needs to be drawn, for which this default spinner should be stopped with `progressObj.stop()` and image-sequencer is informed about the custom progress bar with `progressObj.overrideFlag = true;` following which this object can be overriden with custom progress object.
### Module example
See existing module `green-channel` for an example: https://github.com/publiclab/image-sequencer/tree/master/src/modules/GreenChannel/Module.js
The `green-channel` module is included into the core modules here: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
For help integrating, please open an issue.

View File

@@ -1,6 +1,7 @@
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify');
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
@@ -16,6 +17,7 @@ module.exports = function(grunt) {
files: [
'src/*.js',
'src/*/*.js',
'src/*/*/*.js',
'Gruntfile.js'
],
tasks: [ 'build:js' ]
@@ -27,6 +29,13 @@ module.exports = function(grunt) {
src: ['src/ImageSequencer.js'],
dest: 'dist/image-sequencer.js'
}
},
uglify: {
dist: {
src: ['./dist/image-sequencer.js'],
dest: './dist/image-sequencer.min.js'
}
}
});
@@ -35,7 +44,8 @@ module.exports = function(grunt) {
grunt.registerTask('default', ['watch']);
grunt.registerTask('build', [
'browserify:dist'
'browserify:dist',
'uglify:dist'
]);
};

376
README.md
View File

@@ -1,17 +1,18 @@
Image Sequencer
====
aka "Consequencer"
[![Build Status](https://travis-ci.org/publiclab/image-sequencer.svg?branch=master)](https://travis-ci.org/publiclab/image-sequencer)
## Why
Image Sequencer is different from other image processing systems in that it's non-destructive: instead of modifying the original image, it creates a new image at each step in a sequence. This is because it:
Image Sequencer is different from other image processing systems in that it's _non-destructive_: instead of modifying the original image, it **creates a new image at each step in a sequence**. This is because it:
* produces a legible trail of operations, to "show your work" for evidential, educational, or reproducibility reasons
* makes the creation of new tools or "modules" simpler -- each must accept an input image, and produce an output image
* allows many images to be run through the same sequence of steps
* works identically in the browser, on Node.js, and on the commandline
![workflow diagram](https://raw.githubusercontent.com/publiclab/image-sequencer/master/examples/images/diagram-workflows.png)
It is also for prototyping some other related ideas:
@@ -19,12 +20,57 @@ It is also for prototyping some other related ideas:
* test-based image processing -- the ability to create a sequence of steps that do the same task as some other image processing tool, provable with example before/after images to compare with
* logging of each step to produce an evidentiary record of modifications to an original image
* cascading changes -- change an earlier step's settings, and see those changes affect later steps
* "small modules"-based extensibility: see [Contributing](#contributing), below
* "small modules"-based extensibility: see [Contributing](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md)
## Examples:
## Examples
* [Basic example](https://jywarren.github.io/image-sequencer/)
* [NDVI example](https://jywarren.github.io/image-sequencer/examples/ndvi/) - related to [Infragram.org](http://infragram.org)
* [Simple Demo](https://publiclab.github.io/image-sequencer/)
A diagram of this running 5 steps on a single sample image may help explain how it works:
![example workflow](https://raw.githubusercontent.com/publiclab/image-sequencer/master/examples/images/diagram-6-steps.png)
## Jump to:
* [Installation](#installation)
* [Quick Usage](#quick-usage)
* [CLI Usage](#cli-usage)
* [Classic Usage](#classic-usage)
* [Method Chaining](#method-chaining)
* [Multiple Images](#multiple-images)
* [Creating a User Interface](#creating-a-user-interface)
* [Contributing](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md)
* [Submit a Module](https://github.com/publiclab/image-sequencer/blob/master/CONTRIBUTING.md#contributing-modules)
* [Get Demo Bookmarklet](https://publiclab.org/w/imagesequencerbookmarklet)
## Installation
This library works in the browser, in Node, and on the commandline (CLI), which we think is great.
### Browser
Just include [image-sequencer.js](https://publiclab.github.io/image-sequencer/dist/image-sequencer.js) in the Head section of your web page. See the [demo here](https://publiclab.github.io/image-sequencer/)!
### Node (via NPM)
(You must have NPM for this)
Add `image-sequencer` to your list of dependancies and run `$ npm install`
### CLI
Globally install Image Sequencer:
```
$ npm install image-sequencer -g
```
(You should have Node.JS and NPM for this.)
### To run the debug script
```
$ npm run debug invert
```
## Quick Usage
@@ -49,6 +95,47 @@ For example:
sequencer.replaceImage('#photo',['invert','ndvi-red']);
```
## CLI Usage
Image Sequencer also provides a CLI for applying operations to local files. The CLI takes the following arguments:
-i | --image [PATH/URL] | Input image URL. (required)
-s | --step [step-name] | Name of the step to be added. (required)
-b | --basic | Basic mode only outputs the final image
-o | --output [PATH] | Directory where output will be stored. (optional)
-c | --config {object} | Options for the step. (optional)
The basic format for using the CLI is as follows:
```
$ ./index.js -i [PATH] -s step-name
```
*NOTE:* On Windows you'll have to use `node index.js` instead of `./index.js`.
The CLI also can take multiple steps at once, like so:
```
$ ./index.js -i [PATH] -s "step-name-1 step-name-2 ..."
```
But for this, double quotes must wrap the space-separated steps.
Options for the steps can be passed in one line as json in the details option like
```
$ ./index.js -i [PATH] -s "brightness" -d '{"brightness":50}'
```
Or the values can be given through terminal prompt like
<img width="1436" alt="screen shot 2018-02-14 at 5 18 50 pm" src="https://user-images.githubusercontent.com/25617855/36202790-3c6e8204-11ab-11e8-9e17-7f3387ab0158.png">
The CLI is also chainable with other commands using `&&`
```
sequencer -i <Image Path> -s <steps> && mv <Output Image Path> <New path>
```
## Classic Usage
@@ -68,11 +155,26 @@ a name and an image. The method also accepts an optional callback.
```js
sequencer.loadImage(image_src,optional_callback);
```
On `Node.js` the `image_src` may be a DataURI or a local path. On browsers, it
must be a DatURI (or 'selector to image' -- Work in Progress)
On `Node.js` the `image_src` may be a DataURI or a local path or a URL.
return value: **`sequencer`** (To allow method chaining)
On browsers, it may be a DatURI, a local image or a URL (Unless this violates
CORS Restrictions). To sum up, these are accepted:
* Images in the same domain (or directory - for a local implementation)
* CORS-Proof images in another domain.
* DataURLs
return value: **none** (A callback should be used to ensure the image gets loaded)
The callback is called within the scope of a the sequencer. For example:
(addSteps is defined later)
```js
sequencer.loadImage('SRC',function(){
this.addSteps('module-name');
});
```
The `this` refers to all the images added in the parent `loadImages` function only.
In this case, only `'SRC'`.
### Adding steps to the image
@@ -156,6 +258,7 @@ return value: **`sequencer`** (To allow method chaining)
## Method Chaining
Methods can be chained on the Image Sequencer:
* loadImage()/loadImages() can only terminate a chain.
* run() can not be in the middle of the chain.
* If the chain starts with loadImage() or loadImages(), the following methods are
applied only to the newly loaded images.
@@ -164,9 +267,11 @@ be of the form "image<number>". For ex: "image1", "image2", "image3", etc.
Valid Chains:
```js
sequencer.loadImage('red').addSteps('invert').run(function(out){
//do something with otuput.
});
sequencer.loadImage('red',function(){
this.addSteps('invert').run(function(out){
//do something with ouptut.
});
})
sequencer.addSteps(['ndvi-red','invert']).run();
et cetra.
```
@@ -209,7 +314,7 @@ with each image. This is a string literal.
});
```
return value: **`sequencer`** (To allow method chaining)
return value: **none**
### Adding Steps on Multiple Images
@@ -325,204 +430,75 @@ sequencer.insertSteps({
return value: **`sequencer`** (To allow method chaining)
## Contributing
## Creating a User Interface
Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`.
Image Sequencer provides the following events which can be used to generate a UI:
### Contributing modules
* `onSetup` : this event is triggered when a new module is set up. This can be used,
for instance, to generate a DIV element to store the generated image for that step.
* `onDraw` : This event is triggered when Image Sequencer starts drawing the output
for a module. This can be used, for instance, to overlay a loading GIF over the DIV
generated above.
* `onComplete` : This event is triggered when Image Sequencer has drawn the output
for a module. This can be used, for instance, to update the DIV with the new image
and remove the loading GIF generated above.
* `onRemove` : This event is triggered when a module is removed. This can be used,
for instance, to remove the DIV generated above.
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
#### draw()
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
* `module.draw()`
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
How to define these functions:
```js
input = {
src: "datauri here",
format: "jpeg/png/etc"
}
sequencer.setUI({
onSetup: function(step) {},
onDraw: function(step) {},
onComplete: function(step) {},
onRemove: function(step) {}
});
```
The `image` object is essentially the output of the previous step.
These methods can be defined and re-defined at any time, but it is advisable to
set them before any module is added and not change it thereafter. This is because
the `setUI` method will only affect the modules added after `setUI` is called.
The draw method must, when it is complete, pass the output image to the method `this.output = modified_input`, which will send the output to the next module in the chain. For example:
The `onComplete` event is passed on the output of the module.
Image Sequencer provides a namespace `step` for the purpose of UI Creation in
the scope of these definable function. This namespace has the following
predefined properties:
* `step.name` : (String) Name of the step
* `step.ID` : (Number) An ID given to every step of the sequencer, unique throughout.
* `step.imageName` : (String) Name of the image the step is applied to.
* `step.output` : (DataURL String) Output of the step.
* `step.inBrowser` : (Boolean) Whether the client is a browser or not
In addition to these, one might define their own properties, which shall be
accessible across all the event scopes of that step.
For example :
```js
function draw(image) {
// do some stuff with the image
this.output = image;
callback();
}
```
#### Title
For display in the web-based UI, each module may also have a title like `options.title`.
#### Module example
See existing module `green-channel` for an example: https://github.com/jywarren/image-sequencer/tree/master/src/modules/GreenChannel.js
For help integrating, please open an issue.
****
## Development
Notes on development next steps:
### UI
* [ ] add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib
* [ ] it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
* [ ] spinners before panels are complete
* [ ] is there a module for generating forms from parameters?
* [ ] click to expand for all images
* [ ] `ImageSequencer.Renderer` class to manage image output formats and adapters
* [ ] remove step
* [ ] output besides an image -- like `message(txt)` to display to the step's UI
### Modularization
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
* [x] ability to start running at any point -- already works?
* [x] commandline runnability?
* [x] Make available as browserified OR `require()` includable...
* [ ] standardize panel addition with submodule that offers Panel.display(image)
* [ ] allow passing data as data-uri or Image object, or stream, or ndarray or ImageData array, if both of neighboring pair has ability?
* see https://github.com/jywarren/image-sequencer/issues/1
* [ ] ...could we directly include package.json for module descriptions? At least as a fallback.
* [ ] (for node-and-line style UIs) non-linear sequences with Y-splitters
* [ ] `sequencer.addModule('path/to/module.js')` style module addition -- also to avoid browserifying all of Plotly :-P
* [x] remove step
### Testing
* [ ] tests - modules headless; unit tests
* [ ] comparisons with diff
* [ ] testing a module's promised functionality: each module could offer before/after images as part of their API; by running the module on the before image, you should get exactly the after image, comparing with an image diff
### Use cases
* [ ] make an Infragram module that accepts a math expression
### Bugs
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
* we should make defaults a config of the first module
****
## Module Candidates
* https://github.com/linuxenko/rextract.js
* https://www.npmjs.com/package/histogram
* https://github.com/hughsk/flood-fill
* https://www.npmjs.com/package/blink-diff
* smaller and faster: https://www.npmjs.com/package/@schornio/pixelmatch
* https://github.com/yahoo/pngjs-image has lots of useful general-purpose image getters like `image.getLuminosityAtIndex(idx)`
* some way to add in a new image (respecting alpha) -- `add-image` (with blend mode, default `normal`?)
* https://github.com/yuta1984/CannyJS - edge detection
* http://codepen.io/taylorcoffelt/pen/EsCcr - more edge detection
## Ideas
* https://github.com/vicapow/jsqrcode
* https://github.com/jadnco/whirl - scrubbable image sequence player
* non graphics card GL functions could be shimmed with https://github.com/Overv/JSGL
* or this: https://github.com/stackgl/headless-gl
* https://github.com/mattdesl/fontpath-simple-renderer
* output in animated Gif? as a module
### Referencing earlier states
Complex sequences with masking could require accessing previous states (or nonlinearity):
* flood-fill an area
* select only the flooded area
* roundabout: lighten everything to <50%, then flood-fill with black? Not 100% reliable.
* roundabout 2: `flood fill`, then `blink-diff` with original
* then add step which recovers original image, repeat `flood-fill`/`blink-diff` for second region
* reference above masked states in a `mask` module, with `maskModule.draw(image, { getMask: function() { return maskImg } })`
****
**Notes:**
`pattern-fill` module to use patterns in JS canvas:
```js
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img=document.getElementById("lamp");
var pat=ctx.createPattern(img,"repeat");
ctx.rect(0,0,150,100);
ctx.fillStyle=pat;
ctx.fill();
```
Masking:
```js
ctx.save();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(160, 600);
ctx.rect(0, 0, 160, 600);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
```
****
## UI notes:
* visual nodes-and-lines UI: https://github.com/flowhub/the-graph
* https://flowhub.github.io/the-graph/examples/demo-simple.html
```js
settings: {
'threshold': {
type: 'slider',
label: 'Threshold',
default: 50,
min: 0,
max: 100
sequencer.setUI({
onSetup: function(step){
// Create new property "step.image"
step.image = document.createElement('img');
document.body.append(step.image);
},
'colors': {
type: 'select',
label: 'Colors',
options: [
{ name: '0', value: '0', default: true },
{ name: '1', value: '1' },
{ name: '2', value: '2' }
]
onComplete: function(step){
// Access predefined "step.output" and user-defined "step.image"
step.image.src = step.output;
},
onRemove: function(step){
// Access user-defined "step.image"
step.image.remove();
}
}
});
```
Possible web-based commandline interface: https://hyper.is/?
Note: `identity.imageName` is the "name" of that particular image. This name can
be specified while loading the image via `sequencer.loadImage("name","SRC")`. If
not specified, the name of a loaded image defaults to a name like "image1",
"image2", et cetra.
### Path cutting
* threshold
* vectorize
* edge detect
* direction find (vectorize and colorize)
Details of all modules can be sought using `sequencer.modulesInfo()`.
This method returns an object which defines the name and inputs of the modules. If a module name (hyphenated) is passed in the method, then only the details of that module are returned.

36696
dist/image-sequencer.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

1
dist/image-sequencer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

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)

109
examples/demo.css Normal file
View File

@@ -0,0 +1,109 @@
/* https://github.com/theleagueof/league-spartan */
@font-face {
font-family: 'League Spartan';
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot');
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot?#iefix') format('embedded-opentype'),
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff2') format('woff2'),
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff') format('woff'),
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.ttf') format('truetype'),
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.svg#league_spartanbold') format('svg');
font-weight: bold;
font-style: normal;
}
body {
padding: 20px;
margin: 0 auto 200px;
max-width: 1000px;
background: #f8f8fa;
}
h1 {
font-family: 'League Spartan';
color: #445;
}
.header {
text-align: center;
}
.nomargin {
margin: 0 !important;
}
#dropzone {
padding: 30px;
margin: 0 20% 30px;
border: 4px dashed #ccc;
border-radius: 8px;
text-align: center;
color: #444;
}
.hover {
background: #eee;
}
#dropzone input {
max-width: 100%;
}
.step {
margin-bottom: 20px;
}
.step .img-thumbnail {
margin-bottom: 20px;
}
.details {
border-top: 3px solid #444;
padding-left: 6px;
}
.details h3 {
font-family: monospace;
margin-top: 12px;
font-size: 1.3em;
}
.det {
padding: 10px 16px;
text-decoration: italic;
}
.tools {
margin: 10px 0;
}
.load {
padding: 30px;
background: #eee;
border-radius: 8px;
text-align: center;
font-size: 2em;
color: #444;
}
#addStep {
max-width: 500px;
margin: 20px auto;
}
#addStep .labels {
text-align: right;
padding: 6px;
margin: 2px;
font-weight: bold;
}
@media (max-width:768px) {
#addStep .labels {
text-align: left;
}
}
#addStep .add {
text-align: center;
margin: 10px;
}

28
examples/demo.js Normal file
View File

@@ -0,0 +1,28 @@
window.onload = function() {
sequencer = ImageSequencer();
// Load information of all modules (Name, Inputs, Outputs)
var modulesInfo = sequencer.modulesInfo();
// Add modules to the addStep dropdown
for (var m in modulesInfo) {
$("#addStep select").append(
'<option value="' + m + '">' + modulesInfo[m].name + "</option>"
);
}
// UI for each step:
sequencer.setUI(DefaultHtmlStepUi(sequencer));
// UI for the overall demo:
var ui = DefaultHtmlSequencerUi(sequencer);
sequencer.loadImage("images/tulips.png", ui.onLoad);
$("#addStep select").on("change", ui.selectNewStepUi);
$("#addStep button").on("click", ui.addStepUi);
$('body').on('click', 'button.remove', ui.removeStepUi);
// image selection and drag/drop handling from examples/lib/imageSelection.js
setupFileHandling(sequencer);
};

137
examples/fisheye.html Normal file
View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fisheye Removal | Image Sequencer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=UTF8">
<link rel="stylesheet" href="demo.css">
<style media="screen">
.r.18 {
font-size: 18px !important;
}
.m {
margin: 0 5px;
}
div.c.m {
width: 100px;
}
.r span {
width: 30px !important;
}
@media all and (max-width: 1000px) {
div#main {
flex-direction: column;
}
}
</style>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../dist/image-sequencer.js"></script>
</head>
<body>
<div class="wrapper">
<header>
<h2>Image Sequencer</h2>
<h5>FisheyeGl Demo</h5>
</header>
<section class="rh">
<div class="r">
Drag the Slider to adjust values
</div>
<div class="r">
Select an Image <input type="file" id="file" value="">
</div>
<div class="r" id="main">
<div class="c">
<img id="fisheye" src="" alt="">
</div>
<div class="c rh">
<div class="r 18">
<div class="c m 30">Fx:</div>
<input class="c" name="Fx" type="range" min="0" max="2" step="0.01" value="0" />
<span>0</span>
</div>
<div class="r 18">
<div class="c m 30">Fy:</div>
<input class="c" name="Fy" type="range" min="0" max="2" step="0.01" value="0" />
<span>0</span>
</div>
<div class="r 18">
<div class="c m 30">a:</div>
<input class="c" name="a" type="range" min="0" max="4" step="0.01" value="1" />
<span>1</span>
</div>
<div class="r 18">
<div class="c m 30">b:</div>
<input class="c" name="b" type="range" min="0" max="4" step="0.01" value="1" />
<span>1</span>
</div>
<div class="r 18">
<div class="c m 30">scale:</div>
<input class="c" name="scale" type="range" min="0" max="20" step="0.01" value="1" />
<span>1</span>
</div>
<div class="r 18">
<div class="c m 30">FOV x:</div>
<input class="c" name="x" type="range" min="0" max="2" step="0.01" value="1" />
<span>1</span>
</div>
<div class="r 18">
<div class="c m 30">FOV y:</div>
<input class="c" name="y" type="range" min="0" max="2" step="0.01" value="1" />
<span>1</span>
</div>
</div>
</div>
</section>
<footer> <a href="https://github.com/publiclab/image-sequencer">View on GitHub</a> </footer>
</div>
<script type="text/javascript">
window.onload = function() {
sequencer = ImageSequencer();
var image = document.querySelector('#fisheye');
sequencer.setUI({
onComplete: function(step){
image.src = step.output;
}
});
sequencer.loadImage('fisheye','images/grid.png',function(){
this.addSteps('fisheye-gl').run();
});
var inputs = document.querySelectorAll('input[type="range"]')
for(i in inputs)
inputs[i].oninput = function(e) {
e.target.nextSibling.nextSibling.innerHTML = e.target.value;
sequencer.images.fisheye.steps[1].options[e.target.name] = e.target.value;
sequencer.run(1);
}
document.querySelector('#file').onchange = function(e) {
var file = e.target.files[0];
if(!file) return;
var reader = new FileReader();
reader.onload = function() {
sequencer.images.fisheye.steps[0].output.src = reader.result;
sequencer.run(0);
}
reader.readAsDataURL(file);
}
}
</script>
</body>
</html>

1
examples/images/IS-QR.js Normal file

File diff suppressed because one or more lines are too long

BIN
examples/images/IS-QR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
examples/images/load.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
examples/images/monarch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

View File

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

BIN
examples/images/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
examples/images/test.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

File diff suppressed because one or more lines are too long

BIN
examples/images/tulips.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

95
examples/index.html Normal file
View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=UTF8">
<link rel="icon" sizes="192x192" href="../icons/ic_192.png">
<title>Image Sequencer</title>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
<script src="lib/urlHash.js" charset="utf-8"></script>
<script src="lib/imageSelection.js" charset="utf-8"></script>
<script src="lib/defaultHtmlStepUi.js" charset="utf-8"></script>
<script src="lib/defaultHtmlSequencerUi.js" charset="utf-8"></script>
<script src="demo.js" charset="utf-8"></script>
<!-- for crop module: -->
<script src="../node_modules/imgareaselect/jquery.imgareaselect.dev.js"></script>
</head>
<body>
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<!-- for crop module: -->
<link href="../node_modules/imgareaselect/distfiles/css/imgareaselect-default.css" rel="stylesheet">
<link rel="stylesheet" href="demo.css">
<div class="container-fluid">
<header class="text-center">
<h1>Image Sequencer</h1>
<p>
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original image, it
creates a new image at each step in a sequence.
<a href="https://publiclab.org/image-sequencer">Learn more</a>
</p>
<p>
Open Source
<a href="https://github.com/publiclab/image-sequencer">
<i class="fa fa-github"></i>
</a> by
<a href="https://publiclab.org">Public Lab</a>
</p>
</header>
<div id="dropzone">
<p>
<i>Select or drag in an image to start!</i>
</p>
<center>
<input type="file" id="fileInput" value="">
</center>
</div>
<section id="steps" class="row"></section>
<hr />
<section id="addStep" class="panel panel-primary">
<div class="form-inline">
<div class="panel-body">
<div style="text-align:center;">
<select class="form-control input-lg" id="selectStep">
<option value="none" disabled selected>Select a new step...</option>
</select>
<button class="btn btn-success btn-lg" name="add">Add Step</button>
</div>
<br />
<p class="info" style="padding:8px;">Select a new module to add to your sequence.</p>
</div>
</div>
</section>
</div>
<script type="text/javascript">
$(function () {
var sequencer;
})
</script>
</body>
</html>

View File

@@ -0,0 +1,60 @@
function DefaultHtmlSequencerUi(_sequencer, options) {
options = options || {};
var addStepSel = options.addStepSel = options.addStepSel || "#addStep";
var removeStepSel = options.removeStepSel = options.removeStepSel || "button.remove";
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
function onLoad() {
importStepsFromUrlHash();
}
// look up needed steps from Url Hash:
function importStepsFromUrlHash() {
var hash = getUrlHashParameter("steps");
if (hash) {
var stepsFromHash = hash.split(",");
stepsFromHash.forEach(function eachStep(step) {
_sequencer.addSteps(step);
});
_sequencer.run();
}
}
function selectNewStepUi() {
var m = $(addStepSel + " select").val();
$(addStepSel + " .info").html(_sequencer.modulesInfo(m).description);
}
function removeStepUi() {
var index = $(removeStepSel).index(this) + 1;
sequencer.removeSteps(index).run();
// remove from URL hash too
var urlHash = getUrlHashParameter("steps").split(",");
urlHash.splice(index - 1, 1);
setUrlHashParameter("steps", urlHash.join(","));
}
function addStepUi() {
if ($(addStepSel + " select").val() == "none") return;
// add to URL hash too
var hash = getUrlHashParameter("steps") || "";
if (hash != "") hash += ",";
setUrlHashParameter("steps", hash + $(addStepSel + " select").val());
var newStepName = $(addStepSel + " select").val();
_sequencer
.addSteps(newStepName, options)
.run(null);
}
return {
onLoad: onLoad,
importStepsFromUrlHash: importStepsFromUrlHash,
selectNewStepUi: selectNewStepUi,
removeStepUi: removeStepUi,
addStepUi: addStepUi
}
}

View File

@@ -0,0 +1,184 @@
// Set the UI in sequencer. This Will generate HTML based on
// Image Sequencer events :
// onSetup : Called every time a step is added
// onDraw : Called every time a step starts draw
// onComplete : Called every time a step finishes drawing
// onRemove : Called everytime a step is removed
// The variable 'step' stores useful data like input and
// output values, step information.
// See documetation for more details.
function DefaultHtmlStepUi(_sequencer, options) {
options = options || {};
var stepsEl = options.stepsEl || document.querySelector("#steps");
var selectStepSel = options.selectStepSel = options.selectStepSel || "#selectStep";
function onSetup(step) {
if (step.options && step.options.description)
step.description = step.options.description;
step.ui =
'\
<div class="row step">\
<div class="col-md-4 details">\
<h3>' +
step.name +
"</h3>\
<p><i>" +
(step.description || "") +
'</i></p>\
</div>\
<div class="col-md-8">\
<div class="load" style="display:none;"><i class="fa fa-circle-o-notch fa-spin"></i></div>\
<a><img alt="" style="max-width=100%" class="img-thumbnail" /></a>\
</div>\
</div>\
';
var tools =
'<div class="tools btn-group">\
<button confirm="Are you sure?" class="remove btn btn btn-default">\
<i class="fa fa-trash"></i>\
</button>\
</div>';
var parser = new DOMParser();
step.ui = parser.parseFromString(step.ui, "text/html");
step.ui = step.ui.querySelector("div.row");
step.linkElement = step.ui.querySelector("a");
step.imgElement = step.ui.querySelector("a img");
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = _sequencer.modulesInfo(step.name).inputs;
var outputs = _sequencer.modulesInfo(step.name).outputs;
var merged = Object.assign(inputs, outputs); // combine outputs w inputs
for (var paramName in merged) {
var isInput = inputs.hasOwnProperty(paramName);
var html = "";
var inputDesc = isInput ? inputs[paramName] : {};
if (!isInput) {
html += '<span class="output"></span>';
} else if (inputDesc.type.toLowerCase() == "select") {
html += '<select class="form-control" name="' + paramName + '">';
for (var option in inputDesc.values) {
html += "<option>" + inputDesc.values[option] + "</option>";
}
html += "</select>";
} else {
html =
'<input class="form-control" type="' +
inputDesc.type +
'" name="' +
paramName +
'">';
}
var div = document.createElement("div");
div.className = "row";
div.setAttribute("name", paramName);
var description = inputs[paramName].desc || paramName;
div.innerHTML =
"<div class='det'>\
<label for='" +
paramName +
"'>" +
description +
"</label>\
" +
html +
"\
</div>";
step.ui.querySelector("div.details").appendChild(div);
}
$(step.ui.querySelector("div.details")).append(
"<p><button class='btn btn-default btn-save'>Save</button></p>"
);
function saveOptions() {
$(step.ui.querySelector("div.details"))
.find("input,select")
.each(function(i, input) {
step.options[$(input).attr("name")] = input.value;
});
_sequencer.run();
}
saveOptions();
// on clicking Save in the details pane of the step
$(step.ui.querySelector("div.details .btn-save")).click(saveOptions);
}
if (step.name != "load-image")
step.ui
.querySelector("div.details")
.appendChild(
parser.parseFromString(tools, "text/html").querySelector("div")
);
stepsEl.appendChild(step.ui);
}
function onDraw(step) {
$(step.ui.querySelector(".load")).show();
$(step.ui.querySelector("img")).hide();
}
function onComplete(step) {
$(step.ui.querySelector(".load")).hide();
$(step.ui.querySelector("img")).show();
step.imgElement.src = step.output;
step.linkElement.href = step.output;
// TODO: use a generalized version of this
function fileExtension(output) {
return output.split("/")[1].split(";")[0];
}
step.linkElement.download = step.name + "." + fileExtension(step.output);
step.linkElement.target = "_blank";
// fill inputs with stored step options
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = _sequencer.modulesInfo(step.name).inputs;
var outputs = _sequencer.modulesInfo(step.name).outputs;
for (var i in inputs) {
if (
step.options[i] !== undefined &&
inputs[i].type.toLowerCase() === "input"
)
step.ui.querySelector('div[name="' + i + '"] input').value =
step.options[i];
if (
step.options[i] !== undefined &&
inputs[i].type.toLowerCase() === "select"
)
step.ui.querySelector('div[name="' + i + '"] select').value =
step.options[i];
}
for (var i in outputs) {
if (step[i] !== undefined)
step.ui.querySelector('div[name="' + i + '"] input').value =
step[i];
}
}
}
function onRemove(step) {
step.ui.remove();
}
function getPreview() {
return step.imgElement;
}
return {
getPreview: getPreview,
onSetup: onSetup,
onComplete: onComplete,
onRemove: onRemove,
onDraw: onDraw
}
}

View File

@@ -0,0 +1,50 @@
function setupFileHandling(_sequencer, dropzoneId, fileInputId) {
dropzoneId = dropzoneId || "dropzone";
var dropzone = $('#' + dropzoneId);
fileInputId = fileInputId || "fileInput";
var fileInput = $('#' + fileInputId);
var reader = new FileReader();
function handleFile(e) {
e.preventDefault();
e.stopPropagation(); // stops the browser from redirecting.
if (e.target && e.target.files) var file = e.target.files[0];
else var file = e.dataTransfer.files[0];
if(!file) return;
var reader = new FileReader();
reader.onload = function onFileReaderLoad() {
var loadStep = _sequencer.images.image1.steps[0];
loadStep.output.src = reader.result;
_sequencer.run(0);
loadStep.options.step.imgElement.src = reader.result;
}
reader.readAsDataURL(file);
}
fileInput.on('change', handleFile);
dropzone[0].addEventListener('drop', handleFile, false);
dropzone.on('dragover', function onDragover(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}, false);
dropzone.on('dragenter', function onDragEnter(e) {
dropzone.addClass('hover');
});
dropzone.on('dragleave', function onDragLeave(e) {
dropzone.removeClass('hover');
});
}

42
examples/lib/urlHash.js Normal file
View File

@@ -0,0 +1,42 @@
function getUrlHashParameter(param) {
var params = getUrlHashParameters();
return params[param];
}
function getUrlHashParameters() {
var sPageURL = window.location.hash;
if (sPageURL) sPageURL = sPageURL.split('#')[1];
var pairs = sPageURL.split('&');
var object = {};
pairs.forEach(function(pair, i) {
pair = pair.split('=');
if (pair[0] != '') object[pair[0]] = pair[1];
});
return object;
}
// accepts an object like { paramName: value, paramName1: value }
// and transforms to: url.com#paramName=value&paramName1=value
function setUrlHashParameters(params) {
var keys = Object.keys(params);
var values = Object.values(params);
var pairs = [];
keys.forEach(function(key, i) {
if (key != '') pairs.push(keys[i] + '=' + values[i]);
});
var hash = pairs.join('&');
window.location.hash = hash;
}
function setUrlHashParameter(param, value) {
var params = getUrlHashParameters();
params[param] = value;
setUrlHashParameters(params);
}

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>

BIN
icons/ic_144.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
icons/ic_192.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
icons/ic_96.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

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>

172
index.js Normal file → Executable file
View File

@@ -1,11 +1,165 @@
console.log('\x1b[31m%s\x1b[0m',"This is the output of the module");
#!/usr/bin/env node
require('./src/ImageSequencer');
sequencer = ImageSequencer();
sequencer.loadImages({images:{red:'examples/red.jpg'},callback:function(){
sequencer.addSteps(['do-nothing-pix','ndvi-red','invert']);
sequencer.removeSteps(1);
sequencer.insertSteps({
red: [{index: -1, name: 'do-nothing-pix', o:{}}]
sequencer = ImageSequencer({ui: false});
var Spinner = require('ora')
var program = require('commander');
var readlineSync = require('readline-sync');
function exit(message) {
console.error(message);
process.exit(1);
}
program
.version('0.1.0')
.option('-i, --image [PATH/URL]', 'Input image URL')
.option('-s, --step [step-name]', 'Name of the step to be added.')
.option('-o, --output [PATH]', 'Directory where output will be stored.')
.option('-b, --basic','Basic mode outputs only final image')
.option('-c, --config [Object]', 'Options for the step')
.parse(process.argv);
// Parse step into an array to allow for multiple steps.
if(!program.step) exit("No steps passed")
program.step = program.step.split(" ");
// User must input an image.
if(!program.image) exit("Can't read file.")
// User must input an image.
require('fs').access(program.image, function(err){
if(err) exit("Can't read file.")
});
// User must input a step. If steps exist, check that every step is a valid step.
if(!program.step || !validateSteps(program.step))
exit("Please ensure all steps are valid.");
// If there's no user defined output directory, select a default directory.
program.output = program.output || "./output/";
// Set sequencer to log module outputs, if any.
sequencer.setUI({
onComplete: function(step) {
// Get information of outputs.
step.info = sequencer.modulesInfo(step.name);
for (var output in step.info.outputs) {
console.log("["+program.step+"]: "+output+" = "+step[output]);
}
}
});
// Finally, if everything is alright, load the image, add the steps and run the sequencer.
sequencer.loadImages(program.image,function(){
console.warn('\x1b[33m%s\x1b[0m', "Please wait \n output directory generated will be empty until the execution is complete")
//Generate the Output Directory
require('./src/CliUtils').makedir(program.output,()=>{
console.log("Files will be exported to \""+program.output+"\"");
if(program.basic) console.log("Basic mode is enabled, outputting only final image")
// Iterate through the steps and retrieve their inputs.
program.step.forEach(function(step){
var options = Object.assign({}, sequencer.modulesInfo(step).inputs);
// If inputs exists, print to console.
if (Object.keys(options).length) {
console.log("[" + step + "]: Inputs");
}
// If inputs exists, print them out with descriptions.
Object.keys(options).forEach(function(input) {
// The array below creates a variable number of spaces. This is done with (length + 1).
// The extra 4 that makes it (length + 5) is to account for the []: characters
console.log(new Array(step.length + 5).join(' ') + input + ": " + options[input].desc);
});
if(program.config){
try{
program.config = JSON.parse(program.config);
console.log(`The parsed options object: `, program.config);
}
catch(e){
console.error('\x1b[31m%s\x1b[0m',`Options(Config) is not a not valid JSON Fallback activate`);
program.config = false;
console.log(e);
}
}
if(program.config && validateConfig(program.config,options)){
console.log("Now using Options object");
Object.keys(options).forEach(function (input) {
options[input] = program.config[input];
})
}
else{
// If inputs exist, iterate through them and prompt for values.
Object.keys(options).forEach(function(input) {
var value = readlineSync.question("[" + step + "]: Enter a value for " + input + " (" + options[input].type + ", default: " + options[input].default + "): ");
options[input] = value;
});
}
// Add the step and its inputs to the sequencer.
sequencer.addSteps(step, options);
});
var spinnerObj = Spinner('Your Image is being processed..').start();
// Run the sequencer.
sequencer.run(spinnerObj,function(){
// Export all images or final image as binary files.
sequencer.exportBin(program.output,program.basic);
//check if spinner was not overriden stop it
if(!spinnerObj.overrideFlag) {
spinnerObj.succeed()
console.log(`\nDone!!`)
}
});
});
sequencer.run();
}});
});
// Takes an array of steps and checks if they are valid steps for the sequencer.
function validateSteps(steps) {
// Assume all are valid in the beginning.
var valid = true;
steps.forEach(function(step) {
// If any step in the array is not valid (not a property of modulesInfo), set valid to false.
if (!sequencer.modulesInfo().hasOwnProperty(step)) {
valid = false;
}
});
// Return valid. (If all of the steps are valid properties, valid will have remained true).
return valid;
}
//Takes config and options object and checks if all the keys exist in config
function validateConfig(config_,options_){
options_ = Object.keys(options_);
if (
(function(){
for(var input in options_){
if(!config_[options_[input]]){
console.error('\x1b[31m%s\x1b[0m',`Options Object does not have the required details "${options_[input]}" not specified. Fallback case activated`);
return false;
}
}
})()
== false)
return false;
else
return true;
}

View File

@@ -1,10 +1,11 @@
{
"name": "image-sequencer",
"version": "0.0.1",
"version": "1.5.0",
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
"main": "dist/image-sequencer.js",
"main": "src/ImageSequencer.js",
"scripts": {
"test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\""
"debug": "node ./index.js -i ./examples/images/monarch.png -s",
"test": "tape test/**/*.js test/*.js | tap-spec; browserify test/modules/image-sequencer.js test/modules/chain.js test/modules/replace.js | tape-run --render=\"tap-spec\""
},
"repository": {
"type": "git",
@@ -21,27 +22,42 @@
},
"dependencies": {
"bootstrap": "~3.2.0",
"buffer": "~5.0.2",
"commander": "^2.11.0",
"data-uri-to-buffer": "^2.0.0",
"fisheyegl": "^0.1.2",
"font-awesome": "~4.5.0",
"get-pixels": "~3.3.0",
"jquery": "~2",
"urify": "^2.1.0"
"jsqr": "^0.2.2",
"lodash": "^4.17.5",
"ndarray-gaussian-filter": "^1.0.0",
"ora": "^2.0.0",
"pace": "0.0.4",
"readline-sync": "^1.4.7",
"save-pixels": "~2.3.4",
"urify": "^2.1.0",
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2"
},
"devDependencies": {
"base64-stream": "~0.1.3",
"browserify": "13.0.0",
"buffer": "~5.0.2",
"get-pixels": "~3.3.0",
"data-uri-to-buffer": "^2.0.0",
"grunt": "^0.4.5",
"grunt-browserify": "^5.0.0",
"grunt-contrib-concat": "^0.5.0",
"grunt-contrib-uglify-es": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony",
"grunt-contrib-watch": "^0.6.1",
"image-filter-core": "~1.0.0",
"image-filter-threshold": "~1.0.0",
"looks-same": "^3.2.1",
"matchdep": "^0.3.0",
"plotly.js": "~1.21.2",
"save-pixels": "~2.3.4",
"tap-spec": "^4.1.1",
"tape": ">=4.7.0",
"tape-run": "^3.0.0"
"tape-run": "^3.0.0",
"uglify-es": "^3.3.7"
},
"homepage": "https://github.com/publiclab/image-sequencer"
"homepage": "https://github.com/publiclab/image-sequencer",
"bin": {
"sequencer": "./index.js"
}
}

View File

@@ -1,30 +1,31 @@
function AddStep(ref, image, name, o) {
// add steps to the sequencer
function AddStep(_sequencer, image, name, o) {
function addStep(image, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
var moduleInfo = _sequencer.modules[name][1];
var o = ref.copy(o_);
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o_.name || name;
var o = _sequencer.copy(o_);
o.number = _sequencer.options.sequencerCounter++; // gives a unique ID to each step
o.name = o_.name || name || moduleInfo.name;
o.description = o_.description || moduleInfo.description;
o.selector = o_.selector || 'ismod-' + name;
o.container = o_.container || ref.options.selector;
o.container = o_.container || _sequencer.options.selector;
o.image = image;
o.inBrowser = _sequencer.options.inBrowser;
var module = ref.modules[name].call(ref.images,o);
ref.images[image].steps.push(module);
o.step = {
name: o.name,
description: o.description,
ID: o.number,
imageName: o.image,
inBrowser: _sequencer.options.inBrowser,
ui: _sequencer.options.ui,
options: o
};
var UI = _sequencer.events;
var module = _sequencer.modules[name][0](o,UI);
_sequencer.images[image].steps.push(module);
function defaultSetupModule() {
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
selector: o.selector,
title: module.options.title,
id: o.id
});
}
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?)
// tell the UI that a step has been added.
return true;
}

21
src/CliUtils.js Normal file
View File

@@ -0,0 +1,21 @@
const fs = require('fs')
/*
* This function checks if the directory exists, if not it creates one on the given path
* Callback is called with argument error if an error is encountered
*/
function makedir(path,callback){
fs.access(path,function(err){
if(err) fs.mkdir(path,function(err){
if(err) callback(err);
callback();
});
else callback()
});
};
module.exports = exports = {
makedir: makedir
}

60
src/ExportBin.js Executable file
View File

@@ -0,0 +1,60 @@
var fs = require('fs');
var getDirectories = function(rootDir, cb) {
fs.readdir(rootDir, function(err, files) {
var dirs = [];
if(typeof(files)=="undefined" || files.length == 0) {
cb(dirs);
return [];
}
for (var index = 0; index < files.length; ++index) {
var file = files[index];
if (file[0] !== '.') {
var filePath = rootDir + '/' + file;
fs.stat(filePath, function(err, stat) {
if (stat.isDirectory()) {
dirs.push(this.file);
}
if (files.length === (this.index + 1)) {
return cb(dirs);
}
}.bind({index: index, file: file}));
}
}
});
}
module.exports = function ExportBin(dir = "./output/",ref,basic) {
dir = (dir[dir.length-1]=="/") ? dir : dir + "/";
if(ref.options.inBrowser) return false;
fs.access(dir, function(err){
if(err) console.error(err)
});
getDirectories(dir,function(dirs){
var num = 1;
for(var d in dirs){
if(dirs[d].match(/^sequencer(.*)$/)==null) continue;
var n = parseInt(dirs[d].match(/^sequencer(.*)$/)[1]);
num = (n>=num)?(n+1):num;
}
fs.mkdir(dir+'sequencer'+num,function(){
var root = dir+'sequencer'+num+'/';
for(var image in ref.images) {
var steps = ref.images[image].steps;
if(basic){
var datauri = steps.slice(-1)[0].output.src;
var ext = steps.slice(-1)[0].output.format;
var buffer = require('data-uri-to-buffer')(datauri);
fs.writeFile(root+image+"_"+(steps.length-1)+"."+ext,buffer,function(){});
}
else{
for(var i in steps) {
var datauri = steps[i].output.src;
var ext = steps[i].output.format;
var buffer = require('data-uri-to-buffer')(datauri);
fs.writeFile(root+image+"_"+i+"."+ext,buffer,function(){});
}
}
}
})
});
}

View File

@@ -1,24 +1,23 @@
if (typeof window !== 'undefined') {window.$ = window.jQuery = require('jquery'); isBrowser = true}
if (typeof window !== 'undefined') {isBrowser = true}
else {var isBrowser = false}
ImageSequencer = function ImageSequencer(options) {
options = options || {};
options.inBrowser = options.inBrowser || isBrowser;
// if (options.inBrowser) options.ui = options.ui || require('./UserInterface');
options.sequencerCounter = 0;
function objTypeOf(object){
return Object.prototype.toString.call(object).split(" ")[1].slice(0,-1)
}
function log(color,msg) {
if(options.ui!="none") {
if(arguments.length==1) console.log(arguments[0]);
else if(arguments.length==2) console.log(color,msg);
}
}
function copy(a) {
if (!typeof(a) == "object") return a;
if (objTypeOf(a) == "Array") return a.slice();
@@ -31,135 +30,182 @@ ImageSequencer = function ImageSequencer(options) {
}
return a;
}
function makeArray(input) {
return (objTypeOf(input)=="Array")?input:[input];
}
var image,
steps = [],
modules = require('./Modules'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [];
steps = [],
modules = require('./Modules'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [],
events = require('./ui/UserInterface')(),
fs = require('fs');
// if in browser, prompt for an image
// if (options.imageSelect || options.inBrowser) addStep('image-select');
// else if (options.imageUrl) loadImage(imageUrl);
function addSteps(){
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var args = (this.name == "ImageSequencer")?[]:[this.images];
var json_q = {};
for(var arg in arguments){args.push(copy(arguments[arg]));}
json_q = formatInput.call(this_,args,"+");
inputlog.push({method:"addSteps", json_q:copy(json_q)});
for (var i in json_q)
for (var j in json_q[i])
require("./AddStep")(this_,i,json_q[i][j].name,json_q[i][j].o);
for (var j in json_q[i])
require("./AddStep")(this_,i,json_q[i][j].name,json_q[i][j].o);
return this;
}
function removeStep(image,index) {
//remove the step from images[image].steps and redraw remaining images
if(index>0) {
log('\x1b[31m%s\x1b[0m',"Removing "+index+" from "+image);
thisStep = images[image].steps[index];
thisStep.UI.onRemove(thisStep.options.step);
images[image].steps.splice(index,1);
}
//tell the UI a step has been removed
}
function removeSteps(image,index) {
var run = {}, indices;
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var args = (this.name == "ImageSequencer")?[]:[this.images];
for(var arg in arguments) args.push(copy(arguments[arg]));
var json_q = formatInput.call(this_,args,"-");
inputlog.push({method:"removeSteps", json_q:copy(json_q)});
for (var img in json_q) {
indices = json_q[img].sort(function(a,b){return b-a});
run[img] = indices[indices.length-1];
for (var i in indices)
removeStep(img,indices[i]);
removeStep(img,indices[i]);
}
// this.run(run); // This is creating problems
return this;
}
function insertSteps(image, index, name, o) {
var run = {};
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var args = (this.name == "ImageSequencer")?[]:[this.images];
for (var arg in arguments) args.push(arguments[arg]);
var json_q = formatInput.call(this_,args,"^");
inputlog.push({method:"insertSteps", json_q:copy(json_q)});
for (var img in json_q) {
var details = json_q[img];
details = details.sort(function(a,b){return b.index-a.index});
for (var i in details)
require("./InsertStep")(this_,img,details[i].index,details[i].name,details[i].o);
require("./InsertStep")(this_,img,details[i].index,details[i].name,details[i].o);
run[img] = details[details.length-1].index;
}
// this.run(run); // This is Creating issues
return this;
}
function run(spinnerObj,t_image,t_from) {
let progressObj;
if(arguments[0] != 'test'){
progressObj = spinnerObj
delete arguments['0']
}
function run(t_image,t_from) {
log('\x1b[32m%s\x1b[0m',"Running the Sequencer!");
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var args = (this.name == "ImageSequencer")?[]:[this.images];
for (var arg in arguments) args.push(copy(arguments[arg]));
var callback = function() {};
for (var arg in args)
if(objTypeOf(args[arg]) == "Function")
callback = args.splice(arg,1)[0];
if(objTypeOf(args[arg]) == "Function")
callback = args.splice(arg,1)[0];
var json_q = formatInput.call(this_,args,"r");
require('./Run')(this_, json_q, callback);
require('./Run')(this_, json_q, callback,progressObj);
return true;
}
function loadImages() {
var args = [];
var sequencer = this;
for (var arg in arguments) args.push(copy(arguments[arg]));
var json_q = formatInput.call(this,args,"l");
inputlog.push({method:"loadImages", json_q:copy(json_q)});
var loadedimages = this.copy(json_q.loadedimages);
for (var i in json_q.images)
require('./LoadImage')(this,i,json_q.images[i])
json_q.callback();
return {
var ret = {
name: "ImageSequencer Wrapper",
sequencer: this,
addSteps: this.addSteps,
removeSteps: this.removeSteps,
insertSteps: this.insertSteps,
run: this.run,
UI: this.UI,
setUI: this.setUI,
images: loadedimages
};
function load(i) {
if(i==loadedimages.length) {
json_q.callback.call(ret);
return;
}
var img = loadedimages[i];
require('./ui/LoadImage')(sequencer,img,json_q.images[img],function(){
load(++i);
});
}
load(0);
}
function replaceImage(selector,steps,options) {
options = options || {};
return require('./ReplaceImage')(this,selector,steps);
options.callback = options.callback || function() {};
return require('./ReplaceImage')(this,selector,steps,options);
}
function setUI(UI) {
this.events = require('./ui/UserInterface')(UI);
}
var exportBin = function(dir,basic) {
return require('./ExportBin')(dir,this,basic);
}
function modulesInfo(name) {
var modulesdata = {}
if(name == "load-image") return {};
if(arguments.length==0)
for (var modulename in modules) {
modulesdata[modulename] = modules[modulename][1];
}
else modulesdata = modules[name][1];
return modulesdata;
}
return {
//literals and objects
name: "ImageSequencer",
options: options,
inputlog: inputlog,
modules: modules,
images: images,
events: events,
//user functions
loadImages: loadImages,
loadImage: loadImages,
addSteps: addSteps,
@@ -167,14 +213,15 @@ ImageSequencer = function ImageSequencer(options) {
insertSteps: insertSteps,
replaceImage: replaceImage,
run: run,
inputlog: inputlog,
modules: modules,
images: images,
ui: options.ui,
setUI: setUI,
exportBin: exportBin,
modulesInfo: modulesInfo,
//other functions
log: log,
objTypeOf: objTypeOf,
copy: copy
}
}
module.exports = ImageSequencer;

View File

@@ -1,31 +1,29 @@
// insert one or more steps at a given index in the sequencer
function InsertStep(ref, image, index, name, o) {
function insertStep(image, index, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','inserting step \"' + name + '\" to \"' + image + '\" at \"'+index+'\".');
var o = ref.copy(o_);
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o.name || name;
o.selector = o.selector || 'ismod-' + name;
o.container = o.container || ref.options.selector;
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o_.name || name;
o.selector = o_.selector || 'ismod-' + name;
o.container = o_.container || ref.options.selector;
o.image = image;
if(index==-1) index = ref.images[image].steps.length;
var module = ref.modules[name](o);
ref.images[image].steps.splice(index, 0, module);
function defaultSetupModule() {
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
selector: o.selector,
title: module.options.title,
id: o.id
});
}
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?)
// tell the UI that a step has been inserted.
o.step = {
name: o.name,
description: o.description,
url: o.url,
ID: o.number,
imageName: o.image,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui,
options: o
};
var UI = ref.events;
var module = ref.modules[name][0](o,UI);
ref.images[image].steps.splice(index,0,module);
return true;
}

View File

@@ -1,41 +0,0 @@
function LoadImage(ref, name, src) {
function CImage(src) {
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
var image = {
src: datauri,
format: datauri.split(':')[1].split(';')[0].split('/')[1]
}
return image;
}
function loadImage(name, src) {
var image = {
src: src,
steps: [{
options: {
id: ref.options.sequencerCounter++,
name: "load-image",
title: "Load Image"
},
draw: function() {
if(arguments.length==1){
this.output = CImage(arguments[0]);
return true;
}
else if(arguments.length==2) {
this.output = CImage(arguments[0]);
arguments[1]();
return true;
}
return false;
},
output: CImage(src)
}]
};
ref.images[name] = image;
}
return loadImage(name,src);
}
module.exports = LoadImage;

View File

@@ -1,12 +1,44 @@
/*
* Core modules
*/
* Core modules and their info files
*/
module.exports = {
'do-nothing': require('./modules/DoNothing/Module'),
'green-channel': require('./modules/GreenChannel/Module'),
'ndvi-red': require('./modules/NdviRed/Module'),
'do-nothing-pix': require('./modules/DoNothingPix/Module.js'),
'invert': require('./modules/Invert/Module'),
'crop': require('./modules/Crop/Module'),
'segmented-colormap': require('./modules/SegmentedColormap/Module')
'channel': [
require('./modules/Channel/Module'),require('./modules/Channel/info')
],
'brightness': [
require('./modules/Brightness/Module'),require('./modules/Brightness/info')
],
'edge-detect':[
require('./modules/EdgeDetect/Module'),require('./modules/EdgeDetect/info')
],
'ndvi': [
require('./modules/Ndvi/Module'),require('./modules/Ndvi/info')
],
'invert': [
require('./modules/Invert/Module'),require('./modules/Invert/info')
],
'crop': [
require('./modules/Crop/Module'),require('./modules/Crop/info')
],
'colormap': [
require('./modules/Colormap/Module'),require('./modules/Colormap/info')
],
'decode-qr': [
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
],
'fisheye-gl': [
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
],
'dynamic': [
require('./modules/Dynamic/Module'),require('./modules/Dynamic/info')
],
'blur': [
require('./modules/Blur/Module'),require('./modules/Blur/info')
],
'saturation': [
require('./modules/Saturation/Module'),require('./modules/Saturation/info')
],
'average': [
require('./modules/Average/Module'),require('./modules/Average/info')
]
}

View File

@@ -1,21 +1,37 @@
// Uses a given image as input and replaces it with the output.
// Works only in the browser.
function ReplaceImage(ref,selector,steps,options) {
if(!ref.options.inBrowser) return false; // This isn't for Node.js
var tempSequencer = ImageSequencer({ui: false});
var this_ = ref;
var input = document.querySelectorAll(selector);
if (window.hasOwnProperty('$')) var input = $(selector);
else var input = document.querySelectorAll(selector);
var images = [];
for (var i = 0; i < input.length; i++)
for (var i = 0; i < input.length; i++) {
if (input[i] instanceof HTMLImageElement) images.push(input[i]);
for (var i in images) {
var the_image = images[i];
var url = images[i].src;
var ext = url.split('.').pop();
}
function replaceImage (img, steps) {
var url = img.src;
// refactor to filetypeFromUrl()
var ext = url.split('?')[0].split('.').pop();
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url, true);
xmlHTTP.responseType = 'arraybuffer';
xmlHTTP.onload = function(e) {
var arr = new Uint8Array(this.response);
var raw = String.fromCharCode.apply(null,arr);
// in chunks to avoid "RangeError: Maximum call stack exceeded"
// https://github.com/publiclab/image-sequencer/issues/241
// https://stackoverflow.com/a/20048852/1116657
var raw = '';
var i,j,subArray,chunk = 5000;
for (i=0,j=arr.length; i<j; i+=chunk) {
subArray = arr.subarray(i,i+chunk);
raw += String.fromCharCode.apply(null, subArray);
}
var base64 = btoa(raw);
var dataURL="data:image/"+ext+";base64," + base64;
make(dataURL);
@@ -25,11 +41,19 @@ function ReplaceImage(ref,selector,steps,options) {
else make(url);
function make(url) {
this_.loadImage('default',url).addSteps('default',steps).run(function(out){
the_image.src = out;
tempSequencer.loadImage(url, function(){
this.addSteps(steps).run({stop:function(){}},function(out){
img.src = out;
});
});
}
}
for (var i = 0; i < images.length; i++) {
replaceImage(images[i],steps);
if (i == images.length-1)
options.callback();
}
}
module.exports = ReplaceImage;

View File

@@ -1,47 +1,56 @@
function Run(ref, json_q, callback) {
function drawStep(drawarray,pos) {
if(pos==drawarray.length) {
function Run(ref, json_q, callback,progressObj) {
if(!progressObj) progressObj = {stop: function(){}}
function drawStep(drawarray, pos) {
if (pos == drawarray.length && drawarray[pos - 1] !== undefined) {
var image = drawarray[pos-1].image;
if(ref.objTypeOf(callback)=='Function'){
if(ref.objTypeOf(callback) == 'Function') {
var steps = ref.images[image].steps;
var out = steps[steps.length-1].output.src;
callback(out);
return true;
}
}
var image = drawarray[pos].image;
var i = drawarray[pos].i;
var input = ref.images[image].steps[i-1].output;
ref.images[image].steps[i].draw(ref.copy(input),function(){
drawStep(drawarray,++pos);
});
// so we don't run on the loadImage module:
if (drawarray[pos] !== undefined) {
var image = drawarray[pos].image;
var i = drawarray[pos].i;
var input = ref.images[image].steps[i - 1].output;
ref.images[image].steps[i].draw(ref.copy(input), function onEachStep() {
drawStep(drawarray, ++pos);
},progressObj);
}
}
function drawSteps(json_q) {
var drawarray = [];
for (var image in json_q) {
var no_steps = ref.images[image].steps.length;
var init = json_q[image];
for(var i = 0; i < no_steps-init; i++) {
drawarray.push({image: image,i: init+i});
for(var i = 0; i < no_steps - init; i++) {
drawarray.push({ image: image, i: init + i });
}
}
drawStep(drawarray,0);
}
function filter(json_q){
for (var image in json_q) {
if (json_q[image]==0 && ref.images[image].steps.length==1)
if (json_q[image] == 0 && ref.images[image].steps.length == 1)
delete json_q[image];
else if (json_q[image]==0) json_q[image]++;
else if (json_q[image] == 0) json_q[image]++;
}
for (var image in json_q) {
var prevstep = ref.images[image].steps[json_q[image]-1];
var prevstep = ref.images[image].steps[json_q[image] - 1];
while (typeof(prevstep) == "undefined" || typeof(prevstep.output) == "undefined") {
prevstep = ref.images[image].steps[(--json_q[image]) - 1];
}
}
return json_q;
}
var json_q = filter(json_q);
return drawSteps(json_q);
}
module.exports = Run;

View File

@@ -1,41 +0,0 @@
/*
* Default UI for each image-sequencer module
*/
module.exports = function UserInterface(options) {
options = options || {};
options.container = options.container || ".panels";
options.id = options.id;
options.instanceName = options.instanceName;
options.random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000);
options.uniqueSelector = options.uniqueSelector || options.selector + '-' + options.random;
$(options.container).append('<div class="panel ' + options.selector + ' ' + options.uniqueSelector + '" id="sequencer-'+options.id+'"><div class="image"></div></div>');
options.el = options.el || $('.' + options.uniqueSelector);
createLabel(options.el);
// method to remove the UI for a given method, and remove the step
function display(image) {
options.el.find('.image').html(image);
}
// method to remove the UI for a given method, and remove the step
function remove() {
$('div#sequencer-'+options.id).remove();
}
// method to reorder steps, and update the UI
//function move() {}
function createLabel(el) {
if (options.title) el.prepend('<h3 class="title">' + options.title + '</h3> <button class="btn btn-default" onclick="'+options.instanceName+'.removeStep('+options.id+')">Remove Step</button>');
}
return {
el: options.el,
uniqueSelector: options.uniqueSelector,
selector: options.selector,
display: display,
remove: remove
}
}

86
src/modules/Average/Module.js Executable file
View File

@@ -0,0 +1,86 @@
/*
* Average all pixel colors
*/
module.exports = function Average(options, UI){
options = options || {};
options.blur = options.blur || 2
//Tell the UI that a step has been set up
UI.onSetup(options.step);
var output;
options.step.metadata = options.step.metadata || {};
function draw(input,callback,progressObj){
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell the UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a){
return [r,g,b,a]
}
// do the averaging
function extraManipulation(pixels){
var sum = [0,0,0,0];
for (var i = 0; i < pixels.data.length; i += 4) {
sum[0] += pixels.data[i + 0];
sum[1] += pixels.data[i + 1];
sum[2] += pixels.data[i + 2];
sum[3] += pixels.data[i + 3];
}
sum[0] = parseInt(sum[0] / (pixels.data.length / 4));
sum[1] = parseInt(sum[1] / (pixels.data.length / 4));
sum[2] = parseInt(sum[2] / (pixels.data.length / 4));
sum[3] = parseInt(sum[3] / (pixels.data.length / 4));
for (var i = 0; i < pixels.data.length; i += 4) {
pixels.data[i + 0] = sum[0];
pixels.data[i + 1] = sum[1];
pixels.data[i + 2] = sum[2];
pixels.data[i + 3] = sum[3];
}
// report back and store average in metadata:
options.step.metadata.averages = sum;
console.log("average: ", sum);
return pixels;
}
function output(image, datauri, mimetype){
// This output is accessible by Image Sequencer
step.output = {
src: datauri,
format: mimetype
};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step has been drawn.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

6
src/modules/Average/info.json Executable file
View File

@@ -0,0 +1,6 @@
{
"name": "Average",
"description": "Average all pixel color",
"inputs": {
}
}

85
src/modules/Blur/Blur.js Executable file
View File

@@ -0,0 +1,85 @@
module.exports = exports = function(pixels,blur){
let kernel = kernelGenerator(blur,1)
kernel = flipKernel(kernel)
var oldpix = pixels
for(let i=0;i<pixels.shape[0];i++){
for(let j=0;j<pixels.shape[1];j++){
let neighboutPos = getNeighbouringPixelPositions([i,j])
let acc = [0.0,0.0,0.0,0.0]
for(let a = 0; a < kernel.length; a++){
for(let b = 0; b < kernel.length; b++){
acc[0] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],0) * kernel[a][b]);
acc[1] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],1) * kernel[a][b]);
acc[2] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],2) * kernel[a][b]);
acc[3] += (oldpix.get(neighboutPos[a][b][0],neighboutPos[a][b][1],3) * kernel[a][b]);
}
}
pixels.set(i,j,0,acc[0])
pixels.set(i,j,1,acc[1])
pixels.set(i,j,2,acc[2])
}
}
return pixels
//Generates a 3x3 Gaussian kernel
function kernelGenerator(sigma,size){
/*
Trying out a variable radius kernel not working as of now
*/
// const coeff = (1.0/(2.0*Math.PI*sigma*sigma))
// const expCoeff = -1 * (1.0/2.0 * sigma * sigma)
// let e = Math.E
// let result = []
// for(let i = -1 * size;i<=size;i++){
// let arr = []
// for(let j= -1 * size;j<=size;j++){
// arr.push(coeff * Math.pow(e,expCoeff * ((i * i) + (j*j))))
// }
// result.push(arr)
// }
// let sum = result.reduce((sum,val)=>{
// return val.reduce((sumInner,valInner)=>{
// return sumInner+valInner
// })
// })
// result = result.map(arr=>arr.map(val=>(val + 0.0)/(sum + 0.0)))
// return result
return [
[2.0/159.0,4.0/159.0,5.0/159.0,4.0/159.0,2.0/159.0],
[4.0/159.0,9.0/159.0,12.0/159.0,9.0/159.0,4.0/159.0],
[5.0/159.0,12.0/159.0,15.0/159.0,12.0/159.0,5.0/159.0],
[4.0/159.0,9.0/159.0,12.0/159.0,9.0/159.0,4.0/159.0],
[2.0/159.0,4.0/159.0,5.0/159.0,4.0/159.0,2.0/159.0]
]
}
function getNeighbouringPixelPositions(pixelPosition){
let x = pixelPosition[0],y=pixelPosition[1]
let result = []
for(let i=-2;i<=2;i++){
let arr = []
for(let j=-2;j<=2;j++){
arr.push([x + i,y + j])
}
result.push(arr)
}
return result
}
function flipKernel(kernel){
let result = []
for(let i =kernel.length-1;i>=0;i--){
let arr = []
for(let j = kernel[i].length-1;j>=0;j--){
arr.push(kernel[i][j])
}
result.push(arr)
}
return result
}
}

59
src/modules/Blur/Module.js Executable file
View File

@@ -0,0 +1,59 @@
/*
* Blur an Image
*/
module.exports = function Blur(options,UI){
options = options || {};
options.blur = options.blur || 2
//Tell the UI that a step has been set up
UI.onSetup(options.step);
var output;
function draw(input,callback,progressObj){
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell the UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a){
return [r,g,b,a]
}
function extraManipulation(pixels){
pixels = require('./Blur')(pixels,options.blur)
return pixels
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step has been drawn.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

11
src/modules/Blur/info.json Executable file
View File

@@ -0,0 +1,11 @@
{
"name": "Blur",
"description": "Gaussian blur an image by a given value, typically 0-5",
"inputs": {
"blur": {
"type": "integer",
"desc": "amount of gaussian blur(Less blur gives more detail, typically 0-5)",
"default": 2
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Changes the Image Brightness
*/
module.exports = function Brightness(options,UI){
options = options || {};
//Tell the UI that a step has been set up
UI.onSetup(options.step);
var output;
function draw(input,callback,progressObj){
progressObj.stop(true);
progressObj.overrideFlag = true;
/*
In this case progress is handled by changepixel internally otherwise progressObj
needs to be overriden and used
For eg. progressObj = new SomeProgressModule()
*/
// Tell the UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a){
var val = (options.brightness)/100.0
r = val*r<255?val*r:255
g = val*g<255?val*g:255
b = val*b<255?val*b:255
return [r , g, b, a]
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step has been drawn.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "Brightness",
"description": "Change the brightness of the image by given percent value",
"inputs": {
"brightness": {
"type": "integer",
"desc": "% brightness for the new image",
"default": 0
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Display only one color channel
*/
module.exports = function Channel(options,UI) {
options = options || {};
options.channel = options.channel || "green";
// Tell UI that a step has been set up
UI.onSetup(options.step);
var output;
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
if (options.channel == "red") return [r, 0, 0, a];
if (options.channel == "green") return [0, g, 0, a];
if (options.channel == "blue") return [0, 0, b, a];
}
function output(image,datauri,mimetype){
// This output is accesible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step ahs been drawn
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "Channel",
"description": "Displays only one color channel of an image -- default is green",
"inputs": {
"channel": {
"type": "select",
"desc": "Color channel",
"default": "green",
"values": ["red", "green", "blue"]
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Accepts a value from 0-255 and returns the new color-mapped pixel
* from a lookup table, which can be specified as an array of [begin, end]
* gradients, where begin and end are represented as [r, g, b] colors. In
* combination, a lookup table which maps values from 0 - 255 smoothly from black to white looks like:
* [
* [0, [0, 0, 0], [255, 255, 255]],
* [1, [255, 255, 255], [255, 255, 255]]
* ]
*
* Adapted from bgamari's work in Infragram: https://github.com/p-v-o-s/infragram-js/commit/346c97576a07b71a55671d17e0153b7df74e803b
*/
module.exports = function Colormap(value, options) {
options.colormap = options.colormap || colormaps.default;
// if a lookup table is provided as an array:
if(typeof(options.colormap) == "object")
colormapFunction = colormap(options.colormap);
// if a stored colormap is named with a string like "fastie":
else if(colormaps.hasOwnProperty(options.colormap))
colormapFunction = colormaps[options.colormap];
else colormapFunction = colormaps.default;
return colormapFunction(value / 255.00);
}
function colormap(segments) {
return function(x) {
var i, result, x0, x1, xstart, y0, y1, _i, _j, _len, _ref, _ref1, _ref2, _ref3;
_ref = [0, 0], y0 = _ref[0], y1 = _ref[1];
_ref1 = [segments[0][0], 1], x0 = _ref1[0], x1 = _ref1[1];
if (x < x0) {
return y0;
}
for (i = _i = 0, _len = segments.length; _i < _len; i = ++_i) {
_ref2 = segments[i], xstart = _ref2[0], y0 = _ref2[1], y1 = _ref2[2];
x0 = xstart;
if (i === segments.length - 1) {
x1 = 1;
break;
}
x1 = segments[i + 1][0];
if ((xstart <= x && x < x1)) {
break;
}
}
result = [];
for (i = _j = 0, _ref3 = y0.length; 0 <= _ref3 ? _j < _ref3 : _j > _ref3; i = 0 <= _ref3 ? ++_j : --_j) {
result[i] = (x - x0) / (x1 - x0) * (y1[i] - y0[i]) + y0[i];
}
return result;
};
};
var colormaps = {
greyscale: colormap([
[0, [0, 0, 0], [255, 255, 255] ],
[1, [255, 255, 255], [255, 255, 255] ]
]),
default: colormap([
[0, [0, 0, 255], [0, 255, 0] ],
[0.25, [0, 255, 0], [255, 255, 0] ],
[0.50, [0, 255, 255], [255, 255, 0] ],
[0.75, [255, 255, 0], [255, 0, 0] ]
]),
ndvi: colormap([
[0, [0, 0, 255], [38, 195, 195] ],
[0.5, [0, 150, 0], [255, 255, 0] ],
[0.75, [255, 255, 0], [255, 50, 50] ]
]),
stretched: colormap([
[0, [0, 0, 255], [0, 0, 255] ],
[0.1, [0, 0, 255], [38, 195, 195] ],
[0.5, [0, 150, 0], [255, 255, 0] ],
[0.7, [255, 255, 0], [255, 50, 50] ],
[0.9, [255, 50, 50], [255, 50, 50] ]
]),
fastie: colormap([
[0, [255, 255, 255], [0, 0, 0] ],
[0.167, [0, 0, 0], [255, 255, 255] ],
[0.33, [255, 255, 255], [0, 0, 0] ],
[0.5, [0, 0, 0], [140, 140, 255] ],
[0.55, [140, 140, 255], [0, 255, 0] ],
[0.63, [0, 255, 0], [255, 255, 0] ],
[0.75, [255, 255, 0], [255, 0, 0] ],
[0.95, [255, 0, 0], [255, 0, 255] ]
])
}

View File

@@ -0,0 +1,54 @@
module.exports = function Colormap(options,UI) {
options = options || {};
// Tell the UI that a step has been set up.
UI.onSetup(options.step);
var output;
// This function is called on every draw.
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell the UI that the step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
var combined = (r + g + b) / 3.000;
var res = require('./Colormap')(combined, options);
return [res[0], res[1], res[2], 255];
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
// This output is accessible by the UI
options.step.output = datauri;
// Tell the UI that the draw is complete
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "Colormap",
"description": "Maps brightness values (average of red, green & blue) to a given color lookup table, made up of a set of one more color gradients.\n\nFor example, 'cooler' colors like blue could represent low values, while 'hot' colors like red could represent high values.",
"inputs": {
"colormap": {
"type": "select",
"desc": "Name of the Colormap",
"default": "default",
"values": ["default","greyscale","stretched","fastie"]
}
}
}

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);
});
});
}
};

View File

@@ -13,30 +13,65 @@
* y = options.y
* y = options.y + options.h
*/
module.exports = function CropModule(options) {
options = options || {};
options.title = "Crop Image";
var this_ = this;
var output
module.exports = function CropModule(options, UI) {
function draw(input,callback) {
// TODO: we could also set this to {} if nil in AddModule.js to avoid this line:
options = options || {};
const this_ = this;
// Tell the UI that a step has been added
UI.onSetup(options.step); // we should get UI to return the image thumbnail so we can attach our own UI extensions
require('./Crop')(input,options,function(out,format){
this_.output = {
src: out,
format: format
}
callback();
});
// add our custom in-module html ui:
if (options.step.inBrowser) var ui = require('./Ui.js')(options.step, UI);
var output,
setupComplete = false;
// This function is caled everytime the step has to be redrawn
function draw(input,callback) {
}
// Tell the UI that the step has been triggered
UI.onDraw(options.step);
var step = this;
return {
options: options,
draw: draw,
output: output
}
}
// save the input image;
// TODO: this should be moved to module API to persist the input image
options.step.input = input.src;
require('./Crop')(input, options, function(out, format){
// This output is accessible to Image Sequencer
step.output = {
src: out,
format: format
}
// This output is accessible to the UI
options.step.output = out;
// Tell the UI that the step has been drawn
UI.onComplete(options.step);
// we should do this via event/listener:
if (ui && ui.hide) ui.hide();
// start custom UI setup (draggable UI)
// only once we have an input image
if (setupComplete === false && options.step.inBrowser) {
setupComplete = true;
ui.setup();
}
// Tell Image Sequencer that step has been drawn
callback();
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

97
src/modules/Crop/Ui.js Normal file
View File

@@ -0,0 +1,97 @@
// hide on save
module.exports = function CropModuleUi(step, ui) {
let inputWidth = 0,
inputHeight = 0;
// We don't have input image dimensions at the
// time of setting up the UI; that comes when draw() is triggered.
// So we trigger setup only on first run of draw()
// TODO: link this to an event rather than an explicit call in Module.js
function setup() {
let x = 0,
y = 0;
// display original uncropped input image on initial setup
showOriginal()
inputWidth = Math.floor(imgEl().naturalWidth);
inputHeight = Math.floor(imgEl().naturalHeight);
// display with 50%/50% default crop:
setOptions(x, y, inputWidth, inputHeight);
$(imgEl()).imgAreaSelect({
handles: true,
x1: x,
y1: y,
x2: x + inputWidth / 2,
y2: y + inputHeight / 2,
// when selection is complete
onSelectEnd: function onSelectEnd(img, selection) {
// assign crop values to module UI form inputs:
let converted = convertToNatural(
selection.x1,
selection.y1,
selection.width,
selection.height
);
setOptions(
converted[0],
converted[1],
converted[2],
converted[3]
);
}
});
}
function convertToNatural(_x, _y, _width, _height) {
let displayWidth = $(imgEl()).width(),
displayHeight = $(imgEl()).height();
// return in same order [ x, y, width, height ]:
return [
Math.floor(( _x / displayWidth ) * inputWidth),
Math.floor(( _y / displayHeight ) * inputHeight),
Math.floor(( _width / displayWidth ) * inputWidth),
Math.floor(( _height / displayHeight ) * inputHeight)
]
}
function remove() {
$(imgEl()).imgAreaSelect({
remove: true
});
}
function hide() {
// then hide the draggable UI
$(imgEl()).imgAreaSelect({
hide: true
});
}
// step.imgSelector is not defined, imgElement is:
function imgEl() {
return step.imgElement;
}
function setOptions(x1, y1, width, height) {
let options = $($(imgEl()).parents()[2]).find("input");
options[0].value = x1;
options[1].value = y1;
options[2].value = width;
options[3].value = height;
}
// replaces currently displayed output thumbnail with the input image, for ui dragging purposes
function showOriginal() {
step.imgElement.src = step.input;
}
return {
setup: setup,
remove: remove,
hide: hide
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "Crop",
"description": "Crop image to given x, y, w, h in pixels, measured from top left",
"url": "https://github.com/publiclab/image-sequencer/tree/master/MODULES.md",
"inputs": {
"x": {
"type": "integer",
"desc": "X-position (measured from left) from where cropping starts",
"default": 0
},
"y": {
"type": "integer",
"desc": "Y-position (measured from top) from where cropping starts",
"default": 0
},
"w": {
"type": "integer",
"desc": "Width of crop",
"default": "(50%)"
},
"h": {
"type": "integer",
"desc": "Height of crop",
"default": "(50%)"
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Decodes QR from a given image.
*/
module.exports = function DoNothing(options,UI) {
options = options || {};
// Tell the UI that a step has been added
UI.onSetup(options.step);
var output;
var jsQR = require('jsqr');
var getPixels = require('get-pixels');
// This function is called everytime a step has to be redrawn
function draw(input,callback) {
UI.onDraw(options.step);
var step = this;
getPixels(input.src,function(err,pixels){
if(err) throw err;
var w = pixels.shape[0];
var h = pixels.shape[1];
var decoded = jsQR.decodeQRFromImage(pixels.data,w,h);
// This output is accessible to Image Sequencer
step.output = input;
step.output.data = decoded;
// Tell Image Sequencer that this step is complete
callback();
// These values are accessible to the UI
options.step.output = input.src;
options.step.qrval = decoded;
// Tell the UI that the step is complete and output is set
UI.onComplete(options.step);
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "Decode QR",
"description": "Search for and decode a QR code in the image",
"inputs": {
},
"outputs": {
"qrval": {
"type": "text"
}
}
}

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
}
}

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
}
}

View File

@@ -0,0 +1,95 @@
module.exports = function Dynamic(options,UI) {
options = options || {};
// Tell the UI that a step has been set up.
UI.onSetup(options.step);
var output;
// This function is called on every draw.
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell the UI that the step is being drawn
UI.onDraw(options.step);
var step = this;
// start with monochrome, but if options.red, options.green, and options.blue are set, accept them too
options.monochrome = options.monochrome || "(R+G+B)/3";
function generator(expression) {
var func = 'f = function (r, g, b, a) { var R = r, G = g, B = b, A = a;'
func = func + 'return ';
func = func + expression + '}';
var f;
eval(func);
return f;
}
var channels = ['red', 'green', 'blue', 'alpha'];
channels.forEach(function(channel) {
if (options.hasOwnProperty(channel)) options[channel + '_function'] = generator(options[channel]);
else if (channel === 'alpha') options['alpha_function'] = function() { return 255; }
else options[channel + '_function'] = generator(options.monochrome);
});
function changePixel(r, g, b, a) {
/* neighbourpixels can be calculated by
this.getNeighbourPixel.fun(x,y) or this.getNeighborPixel.fun(x,y)
*/
var combined = (r + g + b) / 3.000;
return [
options.red_function(r, g, b, a),
options.green_function(r, g, b, a),
options.blue_function(r, g, b, a),
options.alpha_function(r, g, b, a),
];
}
/* Functions to get the neighbouring pixel by position (x,y) */
function getNeighbourPixel(pixels,curX,curY,distX,distY){
return [
pixels.get(curX+distX,curY+distY,0)
,pixels.get(curX+distX,curY+distY,1)
,pixels.get(curX+distX,curY+distY,2)
,pixels.get(curX+distX,curY+distY,3)
]
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
// This output is accessible by the UI
options.step.output = datauri;
// Tell the UI that the draw is complete
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
getNeighbourPixel: getNeighbourPixel,
getNeighborPixel: getNeighbourPixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,26 @@
{
"name": "Dynamic",
"description": "A module which accepts JavaScript math expressions to produce each color channel based on the original image's color. See <a href='https://publiclab.org/wiki/infragram-sandbox'>Infragrammar</a>.",
"inputs": {
"red": {
"type": "input",
"desc": "Expression to return for red channel with R, G, B, and A inputs",
"default": "r"
},
"green": {
"type": "input",
"desc": "Expression to return for green channel with R, G, B, and A inputs",
"default": "g"
},
"blue": {
"type": "input",
"desc": "Expression to return for blue channel with R, G, B, and A inputs",
"default": "b"
},
"monochrome (fallback)": {
"type": "input",
"desc": "Expression to return with R, G, B, and A inputs; fallback for other channels if none provided",
"default": "r + g + b"
}
}
}

View File

@@ -0,0 +1,179 @@
const _ = require('lodash')
//define kernels for the sobel filter
const kernelx = [[-1,0,1],[-2,0,2],[-1,0,1]],
kernely = [[-1,-2,-1],[0,0,0],[1,2,1]]
let angles = []
let mags = []
let strongEdgePixels = []
let weakEdgePixels = []
let notInUI
module.exports = exports = function(pixels,highThresholdRatio,lowThresholdRatio,inBrowser){
notInUI = !inBrowser
for(var x = 0; x < pixels.shape[0]; x++) {
angles.push([])
mags.push([])
for(var y = 0; y < pixels.shape[1]; y++) {
var result = changePixel(
pixels,
pixels.get(x,y,0),
pixels.get(x, y, 3),
x,
y
)
let pixel = result.pixel
pixels.set(x, y, 0, pixel[0]);
pixels.set(x, y, 1, pixel[1]);
pixels.set(x, y, 2, pixel[2]);
pixels.set(x, y, 3, pixel[3]);
mags.slice(-1)[0].push(pixel[3])
angles.slice(-1)[0].push(result.angle)
}
}
return hysteresis(doubleThreshold(nonMaxSupress(pixels),highThresholdRatio,lowThresholdRatio))
}
//changepixel function that convolutes every pixel (sobel filter)
function changePixel(pixels,val,a,x,y){
let magX = 0.0
for(let a = 0; a < 3; a++){
for(let b = 0; b < 3; b++){
let xn = x + a - 1;
let yn = y + b - 1;
magX += pixels.get(xn,yn,0) * kernelx[a][b];
}
}
let magY = 0.0
for(let a = 0; a < 3; a++){
for(let b = 0; b < 3; b++){
let xn = x + a - 1;
let yn = y + b - 1;
magY += pixels.get(xn,yn,0) * kernely[a][b];
}
}
let mag = Math.sqrt(Math.pow(magX,2) + Math.pow(magY,2))
let angle = Math.atan2(magY,magX)
return {
pixel:
[val,val,val,mag],
angle: angle
}
}
//Non Maximum Supression without interpolation
function nonMaxSupress(pixels) {
angles = angles.map((arr)=>arr.map(convertToDegrees))
for(let i = 1;i<pixels.shape[0]-1;i++){
for(let j=1;j<pixels.shape[1]-1;j++){
let angle = angles[i][j]
let pixel = pixels.get(i,j)
if ((angle>=-22.5 && angle<=22.5) ||
(angle<-157.5 && angle>=-180))
if ((mags[i][j]>= mags[i][j+1]) &&
(mags[i][j] >= mags[i][j-1]))
pixels.set(i,j,3,mags[i][j])
else
pixels.set(i,j,3,0)
else if ((angle>=22.5 && angle<=67.5) ||
(angle<-112.5 && angle>=-157.5))
if ((mags[i][j] >= mags[i+1][j+1]) &&
(mags[i][j] >= mags[i-1][j-1]))
pixels.set(i,j,3,mags[i][j])
else
pixels.set(i,j,3,0)
else if ((angle>=67.5 && angle<=112.5) ||
(angle<-67.5 && angle>=-112.5))
if ((mags[i][i] >= mags[i+1][j]) &&
(mags[i][j] >= mags[i][j]))
pixels.set(i,j,3,mags[i][j])
else
pixels.set(i,j,3,0)
else if ((angle>=112.5 && angle<=157.5) ||
(angle<-22.5 && angle>=-67.5))
if ((mags[i][j] >= mags[i+1][j-1]) &&
(mags[i][j] >= mags[i-1][j+1]))
pixels.set(i,j,3,mags[i][j])
else
pixels.set(i,j,3,0)
}
}
return pixels
}
//Converts radians to degrees
var convertToDegrees = radians => (radians * 180)/Math.PI
//Finds the max value in a 2d array like mags
var findMaxInMatrix = arr => Math.max(...arr.map(el=>el.map(val=>!!val?val:0)).map(el=>Math.max(...el)))
//Applies the double threshold to the image
function doubleThreshold(pixels,highThresholdRatio,lowThresholdRatio){
const highThreshold = findMaxInMatrix(mags) * 0.2
const lowThreshold = highThreshold * lowThresholdRatio
for(let i =0;i<pixels.shape[0];i++){
for(let j=0;j<pixels.shape[1];j++){
let pixelPos = [i,j]
mags[i][j]>lowThreshold
?mags[i][j]>highThreshold
?strongEdgePixels.push(pixelPos)
:weakEdgePixels.push(pixelPos)
:pixels.set(i,j,3,0)
}
}
strongEdgePixels.forEach(pix=>pixels.set(pix[0],pix[1],3,255))
return pixels
}
// hysteresis edge tracking algorithm
function hysteresis(pixels){
function getNeighbouringPixelPositions(pixelPosition){
let x = pixelPosition[0],y=pixelPosition[1]
return [[x+1,y+1],
[x+1,y],
[x+1,y-1],
[x,y+1],
[x,y-1],
[x-1,y+1],
[x-1,y],
[x-1,y-1]]
}
//This can potentially be improved see https://en.wikipedia.org/wiki/Connected-component_labeling
for(weakPixel in weakEdgePixels){
let neighbourPixels = getNeighbouringPixelPositions(weakEdgePixels[weakPixel])
for(pixel in neighbourPixels){
if(strongEdgePixels.find(el=> _.isEqual(el,neighbourPixels[pixel]))) {
pixels.set(weakPixel[0],weakPixel[1],3,255)
weakEdgePixels.splice(weakPixel,weakPixel)
break
}
}
}
weakEdgePixels.forEach(pix=>pixels.set(pix[0],pix[1],3,0))
return pixels
}

View File

@@ -0,0 +1,67 @@
/*
* Detect Edges in an Image
*/
module.exports = function edgeDetect(options,UI) {
options = options || {};
options.blur = options.blur || 2
options.highThresholdRatio = options.highThresholdRatio||0.2
options.lowThresholdRatio = options.lowThresholdRatio||0.15
// Tell UI that a step has been set up.
UI.onSetup(options.step);
var output;
// The function which is called on every draw.
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell UI that a step is being drawn.
UI.onDraw(options.step);
var step = this;
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
function extraManipulation(pixels){
pixels = require('ndarray-gaussian-filter')(pixels,options.blur)
return require('./EdgeUtils')(pixels,options.highThresholdRatio,options.lowThresholdRatio,options.inBrowser)
}
function changePixel(r, g, b, a) {
return [(r+g+b)/3, (r+g+b)/3, (r+g+b)/3, a];
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step has been drawn.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,21 @@
{
"name": "Detect Edges",
"description": "this module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. Read more at: https://en.wikipedia.org/wiki/Canny_edge_detector",
"inputs": {
"blur": {
"type": "integer",
"desc": "amount of gaussian blur(Less blur gives more detail, typically 0-5)",
"default": 2
},
"highThresholdRatio":{
"type": "float",
"desc": "The high threshold ratio for the image",
"default": 0.2
},
"lowThresholdRatio": {
"type": "float",
"desc": "The low threshold value for the image",
"default": 0.15
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Resolves Fisheye Effect
*/
module.exports = function DoNothing(options,UI) {
options = options || {};
var output;
// Tell the UI that a step has been set up.
UI.onSetup(options.step);
require('fisheyegl');
function draw(input,callback) {
// Tell the UI that the step is being drawn
UI.onDraw(options.step);
var step = this;
if (!options.inBrowser) { // This module is only for browser
this.output = input;
callback();
}
else {
// Create a canvas, if it doesn't already exist.
if (!document.querySelector('#image-sequencer-canvas')) {
var canvas = document.createElement('canvas');
canvas.style.display = "none";
canvas.setAttribute('id','image-sequencer-canvas');
document.body.append(canvas);
}
else var canvas = document.querySelector('#image-sequencer-canvas');
distorter = FisheyeGl({
selector: "#image-sequencer-canvas"
});
// Parse the inputs
options.a = parseFloat(options.a) || distorter.lens.a;
options.b = parseFloat(options.b) || distorter.lens.b;
options.Fx = parseFloat(options.Fx) || distorter.lens.Fx;
options.Fy = parseFloat(options.Fy) || distorter.lens.Fy;
options.scale = parseFloat(options.scale) || distorter.lens.scale;
options.x = parseFloat(options.x) || distorter.fov.x;
options.y = parseFloat(options.y) || distorter.fov.y;
// Set fisheyegl inputs
distorter.lens.a = options.a;
distorter.lens.b = options.b;
distorter.lens.Fx = options.Fx;
distorter.lens.Fy = options.Fy;
distorter.lens.scale = options.scale;
distorter.fov.x = options.x;
distorter.fov.y = options.y;
// generate fisheyegl output
distorter.setImage(input.src,function(){
// this output is accessible to Image Sequencer
step.output = {src: canvas.toDataURL(), format: input.format};
// This output is accessible to the UI
options.step.output = step.output.src;
// Tell Image Sequencer and UI that step has been drawn
callback();
UI.onComplete(options.step);
});
}
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,66 @@
{
"name": "Fisheye GL",
"description": "Correct fisheye, or barrel distortion, in images (with WebGL -- adapted from fisheye-correction-webgl by @bluemir).",
"requires": [ "webgl" ],
"inputs": {
"a": {
"type": "float",
"desc": "a parameter",
"default": 1,
"min": 1,
"max": 4
},
"b": {
"type": "float",
"desc": "b parameter",
"default": 1,
"min": 1,
"max": 4
},
"Fx": {
"type": "float",
"desc": "Fx parameter",
"default": 0,
"min": 0,
"max": 4
},
"Fy": {
"type": "float",
"desc": "Fy parameter",
"default": 0,
"min": 0,
"max": 4
},
"scale": {
"type": "float",
"desc": "Image Scaling",
"default": 1.5,
"min": 0,
"max": 20
},
"x": {
"type": "float",
"desc": "FOV x parameter",
"default": 1.5,
"min": 0,
"max": 20
},
"y": {
"type": "float",
"desc": "FOV y parameter",
"default": 1.5,
"min": 0,
"max": 20
},
"fragmentSrc": {
"type": "PATH",
"desc": "Patht to a WebGL fragment shader file",
"default": "(inbuilt)"
},
"vertexSrc": {
"type": "PATH",
"desc": "Patht to a WebGL vertex shader file",
"default": "(inbuilt)"
}
}
}

View File

@@ -1,36 +0,0 @@
/*
* Display only the green channel
*/
module.exports = function GreenChannel(options) {
options = options || {};
options.title = "Green channel only";
options.description = "Displays only the green channel of an image";
var output;
//function setup() {} // optional
function draw(input,callback) {
var this_ = this;
function changePixel(r, g, b, a) {
return [0, g, 0, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
}
}

View File

@@ -1,36 +1,55 @@
/*
* Display only the green channel
* Invert the image
*/
module.exports = function GreenChannel(options) {
module.exports = function Invert(options,UI) {
options = options || {};
options.title = "Invert Colors";
options.description = "Inverts the colors of the image";
// Tell UI that a step has been set up.
UI.onSetup(options.step);
var output;
//function setup() {} // optional
// The function which is called on every draw.
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell UI that a step is being drawn.
UI.onDraw(options.step);
var step = this;
function draw(input,callback) {
var this_ = this;
function changePixel(r, g, b, a) {
return [255-r, 255-g, 255-b, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
// This output is accessible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step has been drawn.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "Invert",
"description": "Inverts the image.",
"inputs": {
}
}

View File

@@ -0,0 +1,59 @@
/*
* NDVI with red filter (blue channel is infrared)
*/
module.exports = function Ndvi(options,UI) {
options = options || {};
options.filter = options.filter || "red";
// Tell the UI that a step has been set up.
UI.onSetup(options.step);
var output;
// The function which is called on every draw.
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell the UI that a step is being drawn.
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
if (options.filter == "red") var ndvi = (b - r) / (1.00 * b + r);
if (options.filter == "blue") var ndvi = (r - b) / (1.00 * b + r);
var x = 255 * (ndvi + 1) / 2;
return [x, x, x, a];
}
function output(image,datauri,mimetype){
// This output is accessible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by the UI.
options.step.output = datauri;
// Tell the UI that step has been drawn succesfully.
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output,
UI:UI
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "NDVI",
"description": "Normalized Difference Vegetation Index, or NDVI, is an image analysis technique used with aerial photography. It's a way to visualize the amounts of infrared and other wavelengths of light reflected from vegetation by comparing ratios of blue and red light absorbed versus green and IR light reflected. NDVI is used to evaluate the health of vegetation in satellite imagery, where it correlates with how much photosynthesis is happening. This is helpful in assessing vegetative health or stress. <a href='https://publiclab.org/ndvi'>Read more</a>.<br /><br/>This is designed for use with red-filtered single camera <a href='http://publiclab.org/infragram'>DIY Infragram cameras</a>; change to 'blue' for blue filters",
"inputs": {
"filter": {
"type": "select",
"desc": "Filter color",
"default": "red",
"values": ["red", "blue"]
}
}
}

View File

@@ -1,33 +0,0 @@
/*
* NDVI with red filter (blue channel is infrared)
*/
module.exports = function NdviRed(options) {
options = options || {};
options.title = "NDVI for red-filtered cameras (blue is infrared)";
var output;
function draw(input,callback) {
var this_ = this;
function changePixel(r, g, b, a) {
var ndvi = (b - r) / (1.00 * b + r);
var x = 255 * (ndvi + 1) / 2;
return [x, x, x, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw
}
}

View File

@@ -0,0 +1,67 @@
/*
* Saturate an image with a value from 0 to 1
*/
module.exports = function Saturation(options,UI) {
options = options || {};
// Tell UI that a step has been set up
UI.onSetup(options.step);
var output;
function draw(input,callback,progressObj) {
progressObj.stop(true);
progressObj.overrideFlag = true;
// Tell UI that a step is being drawn
UI.onDraw(options.step);
var step = this;
function changePixel(r, g, b, a) {
var cR = 0.299;
var cG = 0.587;
var cB = 0.114;
var p = Math.sqrt((cR * (r*r)) + (cG * (g*g)) + (cB * (g*g)));
r = p+(r-p)*(options.saturation);
g = p+(g-p)*(options.saturation);
b = p+(b-p)*(options.saturation);
return [Math.round(r), Math.round(g), Math.round(b), a];
}
function output(image,datauri,mimetype){
// This output is accesible by Image Sequencer
step.output = {src:datauri,format:mimetype};
// This output is accessible by UI
options.step.output = datauri;
// Tell UI that step ahs been drawn
UI.onComplete(options.step);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output,
UI: UI
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "Saturation",
"description": "Change the saturation of the image by given value, from 0-1, with 1 being 100% saturated.",
"inputs": {
"saturation": {
"type": "integer",
"desc": "saturation for the new image between 0 and 2, 0 being black and white and 2 being highly saturated",
"default": 0
}
}
}

View File

@@ -1,32 +0,0 @@
module.exports = function SegmentedColormap(options) {
options = options || {};
options.title = "Segmented Colormap";
var output;
function draw(input,callback) {
var this_ = this;
function changePixel(r, g, b, a) {
var ndvi = (b - r) / (r + b);
var normalized = (ndvi + 1) / 2;
var res = require('./SegmentedColormap')(normalized,options);
return [res[0], res[1], res[2], 255];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
changePixel: changePixel,
format: input.format,
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output
}
}

View File

@@ -1,57 +0,0 @@
/*
* Accepts a normalized ndvi and returns the new color-mapped pixel
*/
module.exports = function SegmentedColormap(normalized,options) {
options.colormap = options.colormap || "default";
if(typeof(options.colormap) == "object")
colormapFunction = segmented_colormap(options.colormap);
else if(colormaps.hasOwnProperty(options.colormap))
colormapFunction = colormaps[options.colormap];
else colormapFunction = colormaps.default;
return colormapFunction(normalized);
}
function segmented_colormap(segments) {
return function(x) {
var i, result, x0, x1, xstart, y0, y1, _i, _j, _len, _ref, _ref1, _ref2, _ref3;
_ref = [0, 0], y0 = _ref[0], y1 = _ref[1];
_ref1 = [segments[0][0], 1], x0 = _ref1[0], x1 = _ref1[1];
if (x < x0) {
return y0;
}
for (i = _i = 0, _len = segments.length; _i < _len; i = ++_i) {
_ref2 = segments[i], xstart = _ref2[0], y0 = _ref2[1], y1 = _ref2[2];
x0 = xstart;
if (i === segments.length - 1) {
x1 = 1;
break;
}
x1 = segments[i + 1][0];
if ((xstart <= x && x < x1)) {
break;
}
}
result = [];
for (i = _j = 0, _ref3 = y0.length; 0 <= _ref3 ? _j < _ref3 : _j > _ref3; i = 0 <= _ref3 ? ++_j : --_j) {
result[i] = (x - x0) / (x1 - x0) * (y1[i] - y0[i]) + y0[i];
}
return result;
};
};
var greyscale_colormap = segmented_colormap([[0, [0, 0, 0], [255, 255, 255]], [1, [255, 255, 255], [255, 255, 255]]]);
var default_colormap = segmented_colormap([[0, [0, 0, 255], [38, 195, 195]], [0.5, [0, 150, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 50, 50]]]);
var stretched_colormap = segmented_colormap([[0, [0, 0, 255], [0, 0, 255]], [0.1, [0, 0, 255], [38, 195, 195]], [0.5, [0, 150, 0], [255, 255, 0]], [0.7, [255, 255, 0], [255, 50, 50]], [0.9, [255, 50, 50], [255, 50, 50]]]);
var fastie_colormap = segmented_colormap([[0, [255, 255, 255], [0, 0, 0]], [0.167, [0, 0, 0], [255, 255, 255]], [0.33, [255, 255, 255], [0, 0, 0]], [0.5, [0, 0, 0], [140, 140, 255]], [0.55, [140, 140, 255], [0, 255, 0]], [0.63, [0, 255, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 0, 0]], [0.95, [255, 0, 0], [255, 0, 255]]]);
var colormaps = {
greyscale: greyscale_colormap,
default: default_colormap,
stretched: stretched_colormap,
fastie: fastie_colormap
}

View File

@@ -3,7 +3,6 @@
*/
module.exports = function ImageThreshold(options) {
options = options || {};
options.title = "Threshold image";
options.threshold = options.threshold || 30;
var image;

View File

@@ -0,0 +1,6 @@
{
"name": "Threshold image",
"description": "...",
"inputs": {
}
}

View File

@@ -1,58 +1,87 @@
/*
* General purpose per-pixel manipulation
* accepting a changePixel() method to remix a pixel's channels
*/
* General purpose per-pixel manipulation
* accepting a changePixel() method to remix a pixel's channels
*/
module.exports = function PixelManipulation(image, options) {
options = options || {};
options.changePixel = options.changePixel || function changePixel(r, g, b, a) {
return [r, g, b, a];
};
//
options.extraManipulation = options.extraManipulation || function extraManipulation(pixels){
return pixels;
}
var getPixels = require("get-pixels"),
savePixels = require("save-pixels"),
base64 = require('base64-stream');
var getPixels = require('get-pixels'),
savePixels = require('save-pixels');
getPixels(image.src, function(err, pixels) {
if(err) {
console.log("Bad image path")
return
console.log('Bad image path', image);
return;
}
if(options.getNeighbourPixel){
options.getNeighbourPixel.fun = function getNeighborPixel(distX,distY) {
return options.getNeighbourPixel(pixels,x,y,distX,distY);
};
}
// iterate through pixels;
// this could possibly be more efficient; see
// TODO: this could possibly be more efficient; see
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181
for(var x = 0; x < pixels.shape[0]; x++) {
for(var y = 0; y < pixels.shape[1]; y++) {
pixel = options.changePixel(
pixels.get(x, y, 0),
pixels.get(x, y, 1),
pixels.get(x, y, 2),
pixels.get(x, y, 3)
);
if (!options.inBrowser) {
try {
var pace = require('pace')((pixels.shape[0] * pixels.shape[1]));
} catch(e){
options.inBrowser = true;
}
}
for (var x = 0; x < pixels.shape[0]; x++) {
for (var y = 0; y < pixels.shape[1]; y++) {
var pixel = options.changePixel(
pixels.get(x, y, 0),
pixels.get(x, y, 1),
pixels.get(x, y, 2),
pixels.get(x, y, 3)
);
pixels.set(x, y, 0, pixel[0]);
pixels.set(x, y, 1, pixel[1]);
pixels.set(x, y, 2, pixel[2]);
pixels.set(x, y, 3, pixel[3]);
if(!options.inBrowser)
pace.op()
}
}
options.format = "jpeg";
// perform any extra operations on the entire array:
if (options.extraManipulation)
pixels = options.extraManipulation(pixels);
// there may be a more efficient means to encode an image object,
// but node modules and their documentation are essentially arcane on this point
w = base64.encode();
var r = savePixels(pixels, options.format);
r.pipe(w).on('finish',function(){
data = w.read().toString();
datauri = 'data:image/' + options.format + ';base64,' + data;
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, options.format, {quality: 100});
r.on('data', function(chunk){
totalLength += chunk.length;
chunks.push(chunk);
});
r.on('end', function(){
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + options.format + ';base64,' + data;
if (options.output) options.output(options.image,datauri,options.format);
if (options.callback) options.callback();
});
});
}
};

105
src/ui/LoadImage.js Normal file
View File

@@ -0,0 +1,105 @@
// special module to load an image into the start of the sequence; used in the HTML UI
function LoadImage(ref, name, src, main_callback) {
function makeImage(datauri) {
var image = {
src: datauri,
format: datauri.split(':')[1].split(';')[0].split('/')[1]
}
return image;
}
function CImage(src, callback) {
var datauri;
if (!!src.match(/^data:/i)) {
datauri = src;
callback(datauri);
}
else if (!ref.options.inBrowser && !!src.match(/^https?:\/\//i)) {
require( src.match(/^(https?):\/\//i)[1] ).get(src,function(res){
var data = '';
var contentType = res.headers['content-type'];
res.setEncoding('base64');
res.on('data',function(chunk) {data += chunk;});
res.on('end',function() {
callback("data:"+contentType+";base64,"+data);
});
});
}
else if (ref.options.inBrowser) {
var ext = src.split('.').pop();
var image = document.createElement('img');
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
image.onload = function() {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
context.drawImage(image,0,0);
datauri = canvas.toDataURL(ext);
callback(datauri);
}
image.src = src;
}
else {
datauri = require('urify')(src);
callback(datauri);
}
}
function loadImage(name, src) {
var step = {
name: "load-image",
description: "This initial step loads and displays the original image without any modifications.<br /><br />To work with a new or different image, drag one into the drop zone.",
ID: ref.options.sequencerCounter++,
imageName: name,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var image = {
src: src,
steps: [{
options: {
id: step.ID,
name: "load-image",
description: "This initial step loads and displays the original image without any modifications.",
title: "Load Image",
step: step
},
UI: ref.events,
draw: function() {
UI.onDraw(options.step);
if(arguments.length==1){
this.output = CImage(arguments[0]);
options.step.output = this.output;
UI.onComplete(options.step);
return true;
}
else if(arguments.length==2) {
this.output = CImage(arguments[0]);
options.step.output = this.output;
arguments[1]();
UI.onComplete(options.step);
return true;
}
return false;
},
}]
};
CImage(src, function(datauri) {
var output = makeImage(datauri);
ref.images[name] = image;
var loadImageStep = ref.images[name].steps[0];
loadImageStep.output = output;
loadImageStep.options.step.output = loadImageStep.output.src;
loadImageStep.UI.onSetup(loadImageStep.options.step);
loadImageStep.UI.onDraw(loadImageStep.options.step);
loadImageStep.UI.onComplete(loadImageStep.options.step);
main_callback();
return true;
});
}
return loadImage(name,src);
}
module.exports = LoadImage;

58
src/ui/UserInterface.js Normal file
View File

@@ -0,0 +1,58 @@
/*
* User Interface Handling Module
*/
module.exports = function UserInterface(events = {}) {
events.onSetup = events.onSetup || function(step) {
if (step.ui == false) {
// No UI
} else if(step.inBrowser) {
// Create and append an HTML Element
console.log("Added Step \""+step.name+"\" to \""+step.imageName+"\".");
} else {
// Create a NodeJS Object
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+step.name+"\" to \""+step.imageName+"\".");
}
}
events.onDraw = events.onDraw || function(step) {
if (step.ui == false) {
// No UI
} else if(step.inBrowser) {
// Overlay a loading spinner
console.log("Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
} else {
// Don't do anything
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
}
}
events.onComplete = events.onComplete || function(step) {
if (step.ui == false) {
// No UI
} else if(step.inBrowser) {
// Update the DIV Element
// Hide the laoding spinner
console.log("Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
} else {
// Update the NodeJS Object
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
}
}
events.onRemove = events.onRemove || function(step) {
if (step.ui == false){
// No UI
} else if(step.inBrowser) {
// Remove the DIV Element
console.log("Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
} else {
// Delete the NodeJS Object
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
}
}
return events;
}

View File

@@ -1,76 +0,0 @@
'use strict';
var fs = require('fs');
var test = require('tape');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
require('../src/ImageSequencer.js');
var sequencer = ImageSequencer({ ui: "none" });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('loadImages/loadImage has a name generator.', function (t){
sequencer.loadImage(red);
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
t.end();
});
test('loadImages/loadImage returns a wrapper.', function (t){
var returnval = sequencer.loadImage(red);
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image2","Image scope is defined");
t.end();
});
test('addSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).addSteps('invert');
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image3","Image scope is defined");
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
t.end();
});
test('addSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.addSteps("image3","ndvi-red");
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image3.steps.length,3,"Step length increased");
t.equal(sequencer.images.image3.steps[2].options.name,"ndvi-red","Correct Step Added");
t.end();
});
test('removeSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).addSteps('invert').removeSteps(1);
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image4","Image scope is defined");
t.equal(sequencer.images.image4.steps.length,1);
t.end();
});
test('removeSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.removeSteps("image3",1);
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image3.steps.length,2);
t.end();
});
test('insertSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).insertSteps(1,'invert');
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
t.equal(returnval.images[0],"image5","Image scope is defined");
t.equal(sequencer.images.image5.steps.length,2);
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
t.end();
});
test('insertSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.insertSteps("image5",1,"ndvi-red");
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image5.steps.length,3);
t.equal(sequencer.images.image5.steps[1].options.name,"ndvi-red","Correct Step Inserrted");
t.end();
});

13
test/cli.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
const cliUtils = require('../src/CliUtils');
const test = require('tape');
test('Output directory is correctly generated',function(t){
cliUtils.makedir('./output/',function(){
require('fs').access('./output/.',function(err){
t.true(!err,"Access the created dir")
t.end()
});
});
});

View File

@@ -1,25 +0,0 @@
'use strict';
var test = require('tape');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
require('../src/ImageSequencer.js');
var sequencer = ImageSequencer({ ui: "none" });
var image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
sequencer.loadImages(image);
sequencer.addSteps(['do-nothing-pix','invert','invert']);
sequencer.run();
test("Inverted image isn't identical", function (t) {
t.notEqual(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[2].output.src);
t.end();
});
test("Twice inverted image is identical to original image", function (t) {
t.equal(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[3].output.src);
t.end();
});

87
test/modules/chain.js Normal file
View File

@@ -0,0 +1,87 @@
'use strict';
var fs = require('fs');
var test = require('tape');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
require('../../src/ImageSequencer.js');
var sequencer = ImageSequencer({ ui: false });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('loadImages/loadImage has a name generator.', function (t){
sequencer.loadImage(red);
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
t.end();
});
test('loadImages/loadImage returns a wrapper in the callback.', function (t){
sequencer.loadImage(red, function() {
var returnval = this;
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image2","Image scope is defined");
t.end();
});
});
test('addSteps is two-way chainable.', function (t){
sequencer.loadImage(red, function(){
var returnval = this;
this.addSteps('invert');
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image3","Image scope is defined");
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
t.end();
});
});
test('addSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.addSteps("image3","ndvi");
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image3.steps.length,3,"Step length increased");
t.equal(sequencer.images.image3.steps[2].options.name,"ndvi","Correct Step Added");
t.end();
});
test('removeSteps is two-way chainable.', function (t){
sequencer.loadImage(red,function(){
var returnval = this;
this.addSteps('invert').removeSteps(1);
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image4","Image scope is defined");
t.equal(sequencer.images.image4.steps.length,1);
t.end();
});
});
test('removeSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.removeSteps("image3",1);
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image3.steps.length,2);
t.end();
});
test('insertSteps is two-way chainable.', function (t){
sequencer.loadImage(red,function() {
var returnval = this;
this.insertSteps(1,'invert');
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
t.equal(returnval.images[0],"image5","Image scope is defined");
t.equal(sequencer.images.image5.steps.length,2);
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
t.end();
});
});
test('insertSteps is two-way chainable without loadImages.', function (t){
var returnval = sequencer.insertSteps("image5",1,"ndvi");
t.equal(returnval.name,"ImageSequencer","Sequencer is returned");
t.equal(sequencer.images.image5.steps.length,3);
t.equal(sequencer.images.image5.steps[1].options.name,"ndvi","Correct Step Inserrted");
t.end();
});

View File

@@ -0,0 +1,83 @@
'use strict';
var test = require('tape');
var looksSame = require('looks-same');
var DataURItoBuffer = require('data-uri-to-buffer');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
require('../../src/ImageSequencer.js');
//require image files as DataURLs so they can be tested alike on browser and Node.
var sequencer = ImageSequencer({ ui: false });
var qr = require('./images/IS-QR.js');
var test_png = require('./images/test.png.js');
var test_gif = require('./images/test.gif.js');
var spinner = require('ora')('').start()
sequencer.loadImages(test_png);
sequencer.addSteps(['invert','invert']);
test("Preload", function(t) {
sequencer.run(spinner,function(){
t.end();
});
});
test("Inverted image isn't identical", function (t) {
var step1 = sequencer.images.image1.steps[0].output.src;
var step2 = sequencer.images.image1.steps[1].output.src;
step1 = DataURItoBuffer(step1);
step2 = DataURItoBuffer(step2);
looksSame(step1,step2,function(err,res){
if(err) console.log(err);
t.equal(res,false);
t.end();
});
});
test("Twice inverted image is identical to original image", function (t) {
var step1 = sequencer.images.image1.steps[0].output.src;
var step3 = sequencer.images.image1.steps[2].output.src;
step1 = DataURItoBuffer(step1);
step3 = DataURItoBuffer(step3);
looksSame(step1,step3,function(err,res){
if(err) console.log(err);
t.equal(res,true);
t.end();
});
});
test("Decode QR module works properly :: setup", function (t) {
sequencer.loadImage(qr,function(){
this.addSteps('decode-qr').run(spinner.start(),function(){
t.end();
});
})
});
test("Decode QR module works properly :: teardown", function (t) {
t.equal("http://github.com/publiclab/image-sequencer",sequencer.images.image2.steps[1].output.data);
t.end();
});
test("PixelManipulation works for PNG images", function (t) {
sequencer.loadImages(test_png,function(){
this.addSteps('invert').run(spinner.start(),function(out){
t.equal(1,1)
t.end();
});
});
});
test("PixelManipulation works for GIF images", function (t) {
sequencer.loadImages(test_gif,function(){
this.addSteps('invert').run(spinner,function(out){
t.equal(1,1)
t.end();
});
});
});
spinner.stop(true)

View File

@@ -6,7 +6,7 @@ var test = require('tape');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
require('../src/ImageSequencer.js');
require('../../src/ImageSequencer.js');
// This function is used to test whether or not any additional global variables are being created
function copy(g,a) {
@@ -25,7 +25,7 @@ function copy(g,a) {
var parent = (typeof(global)==="undefined")?window:global;
var global1 = copy(true,parent);
var sequencer = ImageSequencer({ ui: "none" });
var sequencer = ImageSequencer({ ui: false });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('Image Sequencer has tests', function (t) {
@@ -40,18 +40,39 @@ test('loadImages loads a DataURL image and creates a step.', function (t){
t.end();
});
test('loadImages loads a PATH image and creates a step. (NodeJS)', function (t){
if(sequencer.options.inBrowser){
t.equal("not applicable","not applicable","Not applicable for Browser");
t.end();
}
else {
sequencer.loadImages(red);
test('modulesInfo() returns info for each module', function (t){
var info = sequencer.modulesInfo();
t.equal(Object.keys(info).length, Object.keys(sequencer.modules).length);
t.equal(info.hasOwnProperty(Object.keys(sequencer.modules)[0]), true);
t.equal(info[Object.keys(sequencer.modules)[0]].hasOwnProperty('name'), true);
t.equal(info[Object.keys(sequencer.modules)[0]].hasOwnProperty('inputs'), true);
t.end();
});
if(!sequencer.options.inBrowser)
test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){
require('dns').resolve('www.github.com', function(err) {
if (err) {
console.log("Test aborted due to no internet");
t.end();
}
else {
sequencer.loadImage('URL','https://ccpandhare.github.io/image-sequencer/examples/images/red.jpg', function(){
t.equal(sequencer.images.URL.steps.length, 1, "Initial Step Created");
t.equal(typeof(sequencer.images.URL.steps[0].output.src), "string", "Initial output exists");
t.end();
});
}
});
});
if(!sequencer.options.inBrowser)
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t){
sequencer.loadImages('examples/images/red.jpg');
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
t.equal(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
t.end();
}
});
});
test('loadImage works too.', function (t){
sequencer.loadImage('test2',red);
@@ -61,38 +82,39 @@ test('loadImage works too.', function (t){
});
test('addSteps("image","name") adds a step', function (t) {
sequencer.addSteps('test','do-nothing');
sequencer.addSteps('test','channel');
t.equal(sequencer.images.test.steps.length, 2, "Length of steps increased")
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Added");
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Added");
t.equal(sequencer.images.test.steps[1].options.description, "Displays only one color channel of an image -- default is green", "Step description shown");
t.end();
});
test('addSteps("name") adds a step', function (t) {
sequencer.addSteps('do-nothing');
sequencer.addSteps('channel');
t.equal(sequencer.images.test.steps.length, 3, "Length of steps increased");
t.equal(sequencer.images.test.steps[2].options.name, "do-nothing", "Correct Step Added");
t.equal(sequencer.images.test.steps[2].options.name, "channel", "Correct Step Added");
t.end();
});
test('addSteps(["name"]) adds a step', function (t) {
sequencer.addSteps(['do-nothing','do-nothing-pix']);
sequencer.addSteps(['channel','invert']);
t.equal(sequencer.images.test.steps.length, 5, "Length of steps increased by two")
t.equal(sequencer.images.test.steps[3].options.name, "do-nothing", "Correct Step Added");
t.equal(sequencer.images.test.steps[4].options.name, "do-nothing-pix", "Correct Step Added");
t.equal(sequencer.images.test.steps[3].options.name, "channel", "Correct Step Added");
t.equal(sequencer.images.test.steps[4].options.name, "invert", "Correct Step Added");
t.end();
});
test('addSteps("name",o) adds a step', function (t) {
sequencer.addSteps('do-nothing',{});
sequencer.addSteps('channel',{});
t.equal(sequencer.images.test.steps.length, 6, "Length of steps increased");
t.equal(sequencer.images.test.steps[5].options.name, "do-nothing", "Correct Step Added");
t.equal(sequencer.images.test.steps[5].options.name, "channel", "Correct Step Added");
t.end();
});
test('addSteps("image","name",o) adds a step', function (t) {
sequencer.addSteps('test','do-nothing',{});
sequencer.addSteps('test','channel',{});
t.equal(sequencer.images.test.steps.length, 7, "Length of steps increased");
t.equal(sequencer.images.test.steps[6].options.name, "do-nothing", "Correct Step Added");
t.equal(sequencer.images.test.steps[6].options.name, "channel", "Correct Step Added");
t.end();
});
@@ -115,30 +137,30 @@ test('removeSteps(position) removes steps', function (t) {
});
test('insertSteps("image",position,"module",options) inserts a step', function (t) {
sequencer.insertSteps('test',1,'do-nothing',{});
sequencer.insertSteps('test',1,'channel',{});
t.equal(sequencer.images.test.steps.length, 3, "Length of Steps increased");
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
t.end();
});
test('insertSteps("image",position,"module") inserts a step', function (t) {
sequencer.insertSteps('test',1,'do-nothing');
sequencer.insertSteps('test',1,'channel');
t.equal(sequencer.images.test.steps.length, 4, "Length of Steps increased");
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
t.end();
});
test('insertSteps(position,"module") inserts a step', function (t) {
sequencer.insertSteps(1,'do-nothing');
sequencer.insertSteps(1,'channel');
t.equal(sequencer.images.test.steps.length, 5, "Length of Steps increased");
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
t.end();
});
test('insertSteps({image: {index: index, name: "module", o: options} }) inserts a step', function (t) {
sequencer.insertSteps({test: {index:1, name:'do-nothing', o:{} } });
sequencer.insertSteps({test: {index:1, name:'channel', o:{} } });
t.equal(sequencer.images.test.steps.length, 6, "Length of Steps increased");
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
t.equal(sequencer.images.test.steps[1].options.name, "channel", "Correct Step Inserted");
t.end();
});

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More