Resolve Conflicts; Update DecodeQr Module

This commit is contained in:
Chinmay Pandhare
2017-07-29 04:25:50 +05:30
27 changed files with 41289 additions and 507 deletions

2
.gitignore vendored
View File

@@ -34,3 +34,5 @@ node_modules
*.swp *.swp
todo.txt todo.txt
test.js
output.txt

File diff suppressed because one or more lines are too long

259
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,259 @@
Contributing to Image Sequencer
===
Happily accepting pull requests; to edit the core library, modify files in `./src/`. To build, run `npm install` followed by `grunt build`.
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
## Contributing modules
Any module must look like this :
```js
module.exports = function ModuleName(options,UI) {
options = options || {};
options.title = "Title of the Module";
UI.onSetup(options.step);
var output;
function draw(input,callback) {
UI.onDraw(options.step);
var output = /*do something with the input*/ ;
this.output = output;
options.step.output = output.src;
callback();
UI.onComplete(options.step);
}
return {
options: options,
draw: draw,
output: output,
UI: UI
}
}
```
### options
The object `options` stores some important information. This is how you can accept
input from users. If you require a variable "x" from the user and the user passes
it in, you will be able to access it as `options.x`.
Options also has some in-built properties. The `options.inBrowser` boolean denotes
whether your module is being run on a browser.
### draw()
The draw method is run every time the step is `run` using `sequencer.run()`.
So any calculations must go **into** the `draw()` method's definition.
What is not in the draw method, but is in the `module.exports` is executed only
when the step is added. So whatever external npm modules are to be loaded, or
constant definitions must be done **outside** the `draw()` method's definition.
`draw()` receives two arguments - `input` and `callback` :
* `input` is an object which is essentially the output of the previous step.
```js
input = {
src: "<$DataURL>",
format: "<png|jpeg|gif>"
}
```
* `callback` is a function which is responsible to tell the sequencer that the
step has been "drawn".
When you have done your calculations and produced an image output, you are required
to set `this.output` to an object similar to what the input object was, call
`callback()`, and set `options.step.output` equal to the output DataURL
### UI Methods
The module is responsible to emit various events for the UI to capture. There are
four events in all:
* `UI.onSetup(options.step)` must be emitted when the module is added. So it must be emitted outside the draw method's definition as shown above.
* `UI.onDraw(options.step)` must be emitted whenever the `draw()` method is called. So it should ideally be the first line of the definition of the `draw` method.
* `UI.onComplete(options.step)` must be emitted whenever the output of a draw call
is ready. An argument, that is the DataURL of the output image must be passed in.
* `UI.onRemove(options.step)` is emitted automatically and the module should not emit it.
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
* `module.draw()`
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
```js
input = {
src: "datauri here",
format: "jpeg/png/etc"
}
```
## options.title
For display in the web-based UI, each module may also have a title `options.title`.
### Module example
See existing module `green-channel` for an example: https://github.com/publiclab/image-sequencer/tree/master/src/modules/GreenChannel/Module.js
For help integrating, please open an issue.
****
## Development
Notes on development next steps:
### UI
* [ ] add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib
* [ ] it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
* [ ] spinners before panels are complete
* [ ] is there a module for generating forms from parameters?
* [ ] click to expand for all images
* [ ] `ImageSequencer.Renderer` class to manage image output formats and adapters
* [ ] remove step
* [ ] output besides an image -- like `message(txt)` to display to the step's UI
### Modularization
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
* [x] ability to start running at any point -- already works?
* [x] commandline runnability?
* [x] Make available as browserified OR `require()` includable...
* [ ] standardize panel addition with submodule that offers Panel.display(image)
* [ ] allow passing data as data-uri or Image object, or stream, or ndarray or ImageData array, if both of neighboring pair has ability?
* see https://github.com/jywarren/image-sequencer/issues/1
* [ ] ...could we directly include package.json for module descriptions? At least as a fallback.
* [ ] (for node-and-line style UIs) non-linear sequences with Y-splitters
* [ ] `sequencer.addModule('path/to/module.js')` style module addition -- also to avoid browserifying all of Plotly :-P
* [x] remove step
### Testing
* [x] tests - modules headless; unit tests
* [ ] comparisons with diff
* [ ] testing a module's promised functionality: each module could offer before/after images as part of their API; by running the module on the before image, you should get exactly the after image, comparing with an image diff
### Use cases
* [ ] make an Infragram module that accepts a math expression
### Bugs
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
* we should make defaults a config of the first module
****
## Module Candidates
* https://github.com/linuxenko/rextract.js
* https://www.npmjs.com/package/histogram
* https://github.com/hughsk/flood-fill
* https://www.npmjs.com/package/blink-diff
* smaller and faster: https://www.npmjs.com/package/@schornio/pixelmatch
* https://github.com/yahoo/pngjs-image has lots of useful general-purpose image getters like `image.getLuminosityAtIndex(idx)`
* some way to add in a new image (respecting alpha) -- `add-image` (with blend mode, default `normal`?)
* https://github.com/yuta1984/CannyJS - edge detection
* http://codepen.io/taylorcoffelt/pen/EsCcr - more edge detection
## Ideas
* https://github.com/vicapow/jsqrcode
* https://github.com/jadnco/whirl - scrubbable image sequence player
* non graphics card GL functions could be shimmed with https://github.com/Overv/JSGL
* or this: https://github.com/stackgl/headless-gl
* https://github.com/mattdesl/fontpath-simple-renderer
* output in animated Gif? as a module
### Referencing earlier states
Complex sequences with masking could require accessing previous states (or nonlinearity):
* flood-fill an area
* select only the flooded area
* roundabout: lighten everything to <50%, then flood-fill with black? Not 100% reliable.
* roundabout 2: `flood fill`, then `blink-diff` with original
* then add step which recovers original image, repeat `flood-fill`/`blink-diff` for second region
* reference above masked states in a `mask` module, with `maskModule.draw(image, { getMask: function() { return maskImg } })`
****
**Notes:**
`pattern-fill` module to use patterns in JS canvas:
```js
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img=document.getElementById("lamp");
var pat=ctx.createPattern(img,"repeat");
ctx.rect(0,0,150,100);
ctx.fillStyle=pat;
ctx.fill();
```
Masking:
```js
ctx.save();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(160, 600);
ctx.rect(0, 0, 160, 600);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
```
****
## UI notes:
* visual nodes-and-lines UI: https://github.com/flowhub/the-graph
* https://flowhub.github.io/the-graph/examples/demo-simple.html
```js
settings: {
'threshold': {
type: 'slider',
label: 'Threshold',
default: 50,
min: 0,
max: 100
},
'colors': {
type: 'select',
label: 'Colors',
options: [
{ name: '0', value: '0', default: true },
{ name: '1', value: '1' },
{ name: '2', value: '2' }
]
}
}
```
Possible web-based commandline interface: https://hyper.is/?
### Path cutting
* threshold
* vectorize
* edge detect
* direction find (vectorize and colorize)

284
README.md
View File

@@ -26,6 +26,14 @@ It is also for prototyping some other related ideas:
* [Basic example](https://jywarren.github.io/image-sequencer/) * [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) * [NDVI example](https://jywarren.github.io/image-sequencer/examples/ndvi/) - related to [Infragram.org](http://infragram.org)
## Jump to:
* [Quick Usage](#quick-usage)
* [Classic Usage](#classic-usage)
* [Method Chaining](#method-chaining)
* [Multiple Images](#multiple-images)
* [Creating a User Interface](#creating-a-user-interface)
## Quick Usage ## Quick Usage
Image Sequencer can be used to run modules on an HTML Image Element using the Image Sequencer can be used to run modules on an HTML Image Element using the
@@ -68,11 +76,26 @@ a name and an image. The method also accepts an optional callback.
```js ```js
sequencer.loadImage(image_src,optional_callback); sequencer.loadImage(image_src,optional_callback);
``` ```
On `Node.js` the `image_src` may be a DataURI or a local path. On browsers, it On `Node.js` the `image_src` may be a DataURI or a local path or a URL.
must be a DatURI (or 'selector to image' -- Work in Progress)
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 ### Adding steps to the image
@@ -156,6 +179,7 @@ return value: **`sequencer`** (To allow method chaining)
## Method Chaining ## Method Chaining
Methods can be chained on the Image Sequencer: 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. * run() can not be in the middle of the chain.
* If the chain starts with loadImage() or loadImages(), the following methods are * If the chain starts with loadImage() or loadImages(), the following methods are
applied only to the newly loaded images. applied only to the newly loaded images.
@@ -164,9 +188,11 @@ be of the form "image<number>". For ex: "image1", "image2", "image3", etc.
Valid Chains: Valid Chains:
```js ```js
sequencer.loadImage('red').addSteps('invert').run(function(out){ sequencer.loadImage('red',function(){
//do something with otuput. this.addSteps('invert').run(function(out){
}); //do something with ouptut.
});
})
sequencer.addSteps(['ndvi-red','invert']).run(); sequencer.addSteps(['ndvi-red','invert']).run();
et cetra. et cetra.
``` ```
@@ -209,7 +235,7 @@ with each image. This is a string literal.
}); });
``` ```
return value: **`sequencer`** (To allow method chaining) return value: **none**
### Adding Steps on Multiple Images ### Adding Steps on Multiple Images
@@ -344,10 +370,10 @@ How to define these functions:
```js ```js
sequencer.setUI({ sequencer.setUI({
onSetup: function() {}, onSetup: function(step) {},
onDraw: function() {}, onDraw: function(step) {},
onComplete: function(output) {}, onComplete: function(step) {},
onRemove: function() {} onRemove: function(step) {}
}); });
``` ```
@@ -357,220 +383,40 @@ the `setUI` method will only affect the modules added after `setUI` is called.
The `onComplete` event is passed on the output of the module. The `onComplete` event is passed on the output of the module.
In the scope of all these events, the following variables are present, which Image Sequencer provides a namespace `step` for the purpose of UI Creation in
may be used in generating the UI: the scope of these definable function. This namespace has the following
* The object `identity` predefined properties:
```
identity = {
stepName: "Name of the Step",
stepID: "A unique ID given to the step",
imageName: "The name of the image to which the step is added."
}
```
* The variable `options.inBrowser` which is a Boolean and is `true` if the client is a browser and `false` otherwise.
Note: `identity.imageName` is the "name" of that particular image. This name can be specified * `step.name` : (String) Name of the step
while loading the image via `sequencer.loadImage("name","SRC")`. If not specified, * `step.ID` : (Number) An ID given to every step of the sequencer, unique throughout.
the name of a loaded image defaults to a name like "image1", "image2", et cetra. * `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
## Contributing In addition to these, one might define their own properties, which shall be
accessible across all the event scopes of that step.
Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`. For example :
### Contributing modules
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:
```js ```js
input = { sequencer.setUI({
src: "datauri here", onSetup: function(step){
format: "jpeg/png/etc" // Create new property "step.image"
} step.image = document.createElement('img');
``` document.body.append(step.image);
The `image` object is essentially the output of the previous step.
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:
```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
}, },
'colors': { onComplete: function(step){
type: 'select', // Access predefined "step.output" and user-defined "step.image"
label: 'Colors', step.image.src = step.output;
options: [ },
{ name: '0', value: '0', default: true }, onRemove: function(step){
{ name: '1', value: '1' }, // Access user-defined "step.image"
{ name: '2', value: '2' } 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",
### Path cutting "image2", et cetra.
* threshold
* vectorize
* edge detect
* direction find (vectorize and colorize)

View File

@@ -37117,11 +37117,14 @@ function AddStep(ref, image, name, o) {
o.container = o_.container || ref.options.selector; o.container = o_.container || ref.options.selector;
o.image = image; o.image = image;
var UI = ref.UI({ o.step = {
stepName: o.name, name: o.name,
stepID: o.number, ID: o.number,
imageName: o.image imageName: o.image,
}); inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var UI = ref.events;
var module = ref.modules[name](o,UI); var module = ref.modules[name](o,UI);
ref.images[image].steps.push(module); ref.images[image].steps.push(module);
@@ -37343,9 +37346,7 @@ ImageSequencer = function ImageSequencer(options) {
formatInput = require('./FormatInput'), formatInput = require('./FormatInput'),
images = {}, images = {},
inputlog = [], inputlog = [],
UI; events = require('./UserInterface')();
setUI();
// if in browser, prompt for an image // if in browser, prompt for an image
// if (options.imageSelect || options.inBrowser) addStep('image-select'); // if (options.imageSelect || options.inBrowser) addStep('image-select');
@@ -37370,7 +37371,8 @@ ImageSequencer = function ImageSequencer(options) {
function removeStep(image,index) { function removeStep(image,index) {
//remove the step from images[image].steps and redraw remaining images //remove the step from images[image].steps and redraw remaining images
if(index>0) { if(index>0) {
images[image].steps[index].UI.onRemove(); thisStep = images[image].steps[index];
thisStep.UI.onRemove(thisStep.options.step);
images[image].steps.splice(index,1); images[image].steps.splice(index,1);
} }
//tell the UI a step has been removed //tell the UI a step has been removed
@@ -37434,17 +37436,15 @@ ImageSequencer = function ImageSequencer(options) {
function loadImages() { function loadImages() {
var args = []; var args = [];
var sequencer = this;
for (var arg in arguments) args.push(copy(arguments[arg])); for (var arg in arguments) args.push(copy(arguments[arg]));
var json_q = formatInput.call(this,args,"l"); var json_q = formatInput.call(this,args,"l");
inputlog.push({method:"loadImages", json_q:copy(json_q)}); inputlog.push({method:"loadImages", json_q:copy(json_q)});
var loadedimages = this.copy(json_q.loadedimages); var loadedimages = this.copy(json_q.loadedimages);
// require('./LoadImage')(this,i,json_q.images[i]);
for (var i in json_q.images) var ret = {
require('./LoadImage')(this,i,json_q.images[i])
json_q.callback();
return {
name: "ImageSequencer Wrapper", name: "ImageSequencer Wrapper",
sequencer: this, sequencer: this,
addSteps: this.addSteps, addSteps: this.addSteps,
@@ -37455,6 +37455,19 @@ ImageSequencer = function ImageSequencer(options) {
setUI: this.setUI, setUI: this.setUI,
images: loadedimages images: loadedimages
}; };
function load(i) {
if(i==loadedimages.length) {
json_q.callback.call(ret);
return;
}
var img = loadedimages[i];
require('./LoadImage')(sequencer,img,json_q.images[img],function(){
load(++i);
});
}
load(0);
} }
function replaceImage(selector,steps,options) { function replaceImage(selector,steps,options) {
@@ -37462,9 +37475,8 @@ ImageSequencer = function ImageSequencer(options) {
return require('./ReplaceImage')(this,selector,steps); return require('./ReplaceImage')(this,selector,steps);
} }
function setUI(_UI) { function setUI(UI) {
UI = require('./UserInterface')(_UI,options); this.events = require('./UserInterface')(UI);
return UI;
} }
return { return {
@@ -37474,7 +37486,7 @@ ImageSequencer = function ImageSequencer(options) {
inputlog: inputlog, inputlog: inputlog,
modules: modules, modules: modules,
images: images, images: images,
UI: UI, events: events,
//user functions //user functions
loadImages: loadImages, loadImages: loadImages,
@@ -37508,11 +37520,14 @@ function InsertStep(ref, image, index, name, o) {
if(index==-1) index = ref.images[image].steps.length; if(index==-1) index = ref.images[image].steps.length;
var UI = ref.UI({ o.step = {
stepName: o.name, name: o.name,
stepID: o.number, ID: o.number,
imageName: o.image imageName: o.image,
}); inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var UI = ref.events;
var module = ref.modules[name](o,UI); var module = ref.modules[name](o,UI);
ref.images[image].steps.splice(index,0,module); ref.images[image].steps.splice(index,0,module);
@@ -37524,49 +37539,102 @@ function InsertStep(ref, image, index, name, o) {
module.exports = InsertStep; module.exports = InsertStep;
},{}],116:[function(require,module,exports){ },{}],116:[function(require,module,exports){
function LoadImage(ref, name, src) { function LoadImage(ref, name, src, main_callback) {
function CImage(src) { function makeImage(datauri) {
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
var image = { var image = {
src: datauri, src: datauri,
format: datauri.split(':')[1].split(';')[0].split('/')[1] format: datauri.split(':')[1].split(';')[0].split('/')[1]
} }
return image; 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) { function loadImage(name, src) {
var step = {
name: "load-image",
ID: ref.options.sequencerCounter++,
imageName: name,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var image = { var image = {
src: src, src: src,
steps: [{ steps: [{
options: { options: {
id: ref.options.sequencerCounter++, id: step.ID,
name: "load-image", name: "load-image",
title: "Load Image" title: "Load Image",
step: step
}, },
UI: ref.UI({ UI: ref.events,
stepName: "load-image",
stepID: ref.options.sequencerCounter++,
imageName: name
}),
draw: function() { draw: function() {
UI.onDraw(options.step);
if(arguments.length==1){ if(arguments.length==1){
this.output = CImage(arguments[0]); this.output = CImage(arguments[0]);
options.step.output = this.output;
UI.onComplete(options.step);
return true; return true;
} }
else if(arguments.length==2) { else if(arguments.length==2) {
this.output = CImage(arguments[0]); this.output = CImage(arguments[0]);
options.step.output = this.output;
arguments[1](); arguments[1]();
UI.onComplete(options.step);
return true; return true;
} }
return false; return false;
}, },
output: CImage(src)
}] }]
}; };
CImage(src, function(datauri) {
var output = makeImage(datauri);
ref.images[name] = image; ref.images[name] = image;
ref.images[name].steps[0].UI.onSetup(); var loadImageStep = ref.images[name].steps[0];
ref.images[name].steps[0].UI.onDraw(); loadImageStep.output = output;
ref.images[name].steps[0].UI.onComplete(image.steps[0].output.src); 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); return loadImage(name,src);
@@ -37677,75 +37745,69 @@ module.exports = Run;
},{}],120:[function(require,module,exports){ },{}],120:[function(require,module,exports){
/* /*
* Default UI for each image-sequencer module * User Interface Handling Module
*/ */
module.exports = function UserInterface(UI,options) {
return function userInterface(identity) { module.exports = function UserInterface(events = {}) {
var UI = UI || {}; events.onSetup = events.onSetup || function(step) {
if(step.ui == false) {
UI.onSetup = UI.onSetup || function() {
if(options.ui == false) {
// No UI // No UI
} }
else if(options.inBrowser) { else if(step.inBrowser) {
// Create and append an HTML Element // Create and append an HTML Element
console.log("Added Step \""+identity.stepName+"\" to \""+identity.imageName+"\"."); console.log("Added Step \""+step.name+"\" to \""+step.imageName+"\".");
} }
else { else {
// Create a NodeJS Object // Create a NodeJS Object
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+identity.stepName+"\" to \""+identity.imageName+"\"."); console.log('\x1b[36m%s\x1b[0m',"Added Step \""+step.name+"\" to \""+step.imageName+"\".");
} }
} }
UI.onDraw = UI.onDraw || function() { events.onDraw = events.onDraw || function(step) {
if (options.ui == false) { if (step.ui == false) {
// No UI // No UI
} }
else if(options.inBrowser) { else if(step.inBrowser) {
// Overlay a loading spinner // Overlay a loading spinner
console.log("Drawing Step \""+identity.stepName+"\" on \""+identity.imageName+"\"."); console.log("Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
} }
else { else {
// Don't do anything // Don't do anything
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+identity.stepName+"\" on \""+identity.imageName+"\"."); console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
} }
} }
UI.onComplete = UI.onComplete || function(output) { events.onComplete = events.onComplete || function(step) {
if (options.ui == false) { if (step.ui == false) {
// No UI // No UI
} }
else if(options.inBrowser) { else if(step.inBrowser) {
// Update the DIV Element // Update the DIV Element
// Hide the laoding spinner // Hide the laoding spinner
console.log("Drawn Step \""+identity.stepName+"\" on \""+identity.imageName+"\"."); console.log("Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
} }
else { else {
// Update the NodeJS Object // Update the NodeJS Object
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+identity.stepName+"\" on \""+identity.imageName+"\"."); console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
} }
} }
UI.onRemove = UI.onRemove || function(callback) { events.onRemove = events.onRemove || function(step) {
if(options.ui == false){ if(step.ui == false){
// No UI // No UI
} }
else if(options.inBrowser) { else if(step.inBrowser) {
// Remove the DIV Element // Remove the DIV Element
console.log("Removing Step \""+identity.stepName+"\" of \""+identity.imageName+"\"."); console.log("Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
} }
else { else {
// Delete the NodeJS Object // Delete the NodeJS Object
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+identity.stepName+"\" of \""+identity.imageName+"\"."); console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
} }
} }
return UI; return events;
}
} }
@@ -37809,12 +37871,12 @@ module.exports = function Crop(input,options,callback) {
module.exports = function CropModule(options,UI) { module.exports = function CropModule(options,UI) {
options = options || {}; options = options || {};
options.title = "Crop Image"; options.title = "Crop Image";
UI.onSetup(); UI.onSetup(options.step);
var output var output
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
require('./Crop')(input,options,function(out,format){ require('./Crop')(input,options,function(out,format){
@@ -37822,7 +37884,8 @@ module.exports = function Crop(input,options,callback) {
src: out, src: out,
format: format format: format
} }
UI.onComplete(out); options.step.output = out;
UI.onComplete(options.step);
callback(); callback();
}); });
@@ -37880,14 +37943,17 @@ module.exports = function DoNothing(options,UI) {
module.exports = function DoNothing(options,UI) { module.exports = function DoNothing(options,UI) {
options = options || {}; options = options || {};
options.title = "Do Nothing"; options.title = "Do Nothing";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
this.output = input; this.output = input;
options.step.output = this.output.src;
callback(); callback();
UI.onComplete(this.output.src); UI.onComplete(options.step);
} }
return { return {
@@ -37906,12 +37972,12 @@ module.exports = function DoNothingPix(options,UI) {
options = options || {}; options = options || {};
options.title = "Do Nothing with pixels"; options.title = "Do Nothing with pixels";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -37919,7 +37985,8 @@ module.exports = function DoNothingPix(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype} step.output = {src:datauri,format:mimetype}
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
@@ -37948,12 +38015,12 @@ module.exports = function GreenChannel(options,UI) {
options = options || {}; options = options || {};
options.title = "Green channel only"; options.title = "Green channel only";
options.description = "Displays only the green channel of an image"; options.description = "Displays only the green channel of an image";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -37961,7 +38028,8 @@ module.exports = function GreenChannel(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
@@ -37991,14 +38059,14 @@ module.exports = function GreenChannel(options,UI) {
options = options || {}; options = options || {};
options.title = "Invert Colors"; options.title = "Invert Colors";
options.description = "Inverts the colors of the image"; options.description = "Inverts the colors of the image";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
//function setup() {} // optional //function setup() {} // optional
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -38006,7 +38074,8 @@ module.exports = function GreenChannel(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
@@ -38035,12 +38104,12 @@ module.exports = function NdviRed(options,UI) {
options = options || {}; options = options || {};
options.title = "NDVI for red-filtered cameras (blue is infrared)"; options.title = "NDVI for red-filtered cameras (blue is infrared)";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -38050,7 +38119,8 @@ module.exports = function NdviRed(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
@@ -38075,12 +38145,12 @@ module.exports = function SegmentedColormap(options,UI) {
options = options || {}; options = options || {};
options.title = "Segmented Colormap"; options.title = "Segmented Colormap";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -38091,7 +38161,8 @@ module.exports = function SegmentedColormap(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
@@ -38217,7 +38288,7 @@ module.exports = function PixelManipulation(image, options) {
// but node modules and their documentation are essentially arcane on this point // but node modules and their documentation are essentially arcane on this point
var chunks = []; var chunks = [];
var totalLength = 0; var totalLength = 0;
var r = savePixels(pixels, options.format); var r = savePixels(pixels, options.format, {quality: 100});
r.on('data', function(chunk){ r.on('data', function(chunk){
totalLength += chunk.length; totalLength += chunk.length;

BIN
examples/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

BIN
examples/test.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

1
examples/test.gif.js Normal file

File diff suppressed because one or more lines are too long

1
examples/test.png.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
{ {
"name": "image-sequencer", "name": "image-sequencer",
"version": "0.0.1", "version": "0.1.0",
"description": "A modular JavaScript image manipulation library modeled on a storyboard.", "description": "A modular JavaScript image manipulation library modeled on a storyboard.",
"main": "dist/image-sequencer.js", "main": "src/ImageSequencer.js",
"scripts": { "scripts": {
"test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\"" "test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\""
}, },

View File

@@ -8,11 +8,14 @@ function AddStep(ref, image, name, o) {
o.container = o_.container || ref.options.selector; o.container = o_.container || ref.options.selector;
o.image = image; o.image = image;
var UI = ref.UI({ o.step = {
stepName: o.name, name: o.name,
stepID: o.number, ID: o.number,
imageName: o.image imageName: o.image,
}); inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var UI = ref.events;
var module = ref.modules[name](o,UI); var module = ref.modules[name](o,UI);
ref.images[image].steps.push(module); ref.images[image].steps.push(module);

View File

@@ -42,9 +42,7 @@ ImageSequencer = function ImageSequencer(options) {
formatInput = require('./FormatInput'), formatInput = require('./FormatInput'),
images = {}, images = {},
inputlog = [], inputlog = [],
UI; events = require('./UserInterface')();
setUI();
// if in browser, prompt for an image // if in browser, prompt for an image
// if (options.imageSelect || options.inBrowser) addStep('image-select'); // if (options.imageSelect || options.inBrowser) addStep('image-select');
@@ -69,7 +67,8 @@ ImageSequencer = function ImageSequencer(options) {
function removeStep(image,index) { function removeStep(image,index) {
//remove the step from images[image].steps and redraw remaining images //remove the step from images[image].steps and redraw remaining images
if(index>0) { if(index>0) {
images[image].steps[index].UI.onRemove(); thisStep = images[image].steps[index];
thisStep.UI.onRemove(thisStep.options.step);
images[image].steps.splice(index,1); images[image].steps.splice(index,1);
} }
//tell the UI a step has been removed //tell the UI a step has been removed
@@ -133,17 +132,15 @@ ImageSequencer = function ImageSequencer(options) {
function loadImages() { function loadImages() {
var args = []; var args = [];
var sequencer = this;
for (var arg in arguments) args.push(copy(arguments[arg])); for (var arg in arguments) args.push(copy(arguments[arg]));
var json_q = formatInput.call(this,args,"l"); var json_q = formatInput.call(this,args,"l");
inputlog.push({method:"loadImages", json_q:copy(json_q)}); inputlog.push({method:"loadImages", json_q:copy(json_q)});
var loadedimages = this.copy(json_q.loadedimages); var loadedimages = this.copy(json_q.loadedimages);
// require('./LoadImage')(this,i,json_q.images[i]);
for (var i in json_q.images) var ret = {
require('./LoadImage')(this,i,json_q.images[i])
json_q.callback();
return {
name: "ImageSequencer Wrapper", name: "ImageSequencer Wrapper",
sequencer: this, sequencer: this,
addSteps: this.addSteps, addSteps: this.addSteps,
@@ -154,6 +151,19 @@ ImageSequencer = function ImageSequencer(options) {
setUI: this.setUI, setUI: this.setUI,
images: loadedimages images: loadedimages
}; };
function load(i) {
if(i==loadedimages.length) {
json_q.callback.call(ret);
return;
}
var img = loadedimages[i];
require('./LoadImage')(sequencer,img,json_q.images[img],function(){
load(++i);
});
}
load(0);
} }
function replaceImage(selector,steps,options) { function replaceImage(selector,steps,options) {
@@ -161,9 +171,8 @@ ImageSequencer = function ImageSequencer(options) {
return require('./ReplaceImage')(this,selector,steps); return require('./ReplaceImage')(this,selector,steps);
} }
function setUI(_UI) { function setUI(UI) {
UI = require('./UserInterface')(_UI,options); this.events = require('./UserInterface')(UI);
return UI;
} }
return { return {
@@ -173,7 +182,7 @@ ImageSequencer = function ImageSequencer(options) {
inputlog: inputlog, inputlog: inputlog,
modules: modules, modules: modules,
images: images, images: images,
UI: UI, events: events,
//user functions //user functions
loadImages: loadImages, loadImages: loadImages,

View File

@@ -10,11 +10,14 @@ function InsertStep(ref, image, index, name, o) {
if(index==-1) index = ref.images[image].steps.length; if(index==-1) index = ref.images[image].steps.length;
var UI = ref.UI({ o.step = {
stepName: o.name, name: o.name,
stepID: o.number, ID: o.number,
imageName: o.image imageName: o.image,
}); inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var UI = ref.events;
var module = ref.modules[name](o,UI); var module = ref.modules[name](o,UI);
ref.images[image].steps.splice(index,0,module); ref.images[image].steps.splice(index,0,module);

View File

@@ -1,46 +1,99 @@
function LoadImage(ref, name, src) { function LoadImage(ref, name, src, main_callback) {
function CImage(src) { function makeImage(datauri) {
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
var image = { var image = {
src: datauri, src: datauri,
format: datauri.split(':')[1].split(';')[0].split('/')[1] format: datauri.split(':')[1].split(';')[0].split('/')[1]
} }
return image; 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) { function loadImage(name, src) {
var step = {
name: "load-image",
ID: ref.options.sequencerCounter++,
imageName: name,
inBrowser: ref.options.inBrowser,
ui: ref.options.ui
};
var image = { var image = {
src: src, src: src,
steps: [{ steps: [{
options: { options: {
id: ref.options.sequencerCounter++, id: step.ID,
name: "load-image", name: "load-image",
title: "Load Image" title: "Load Image",
step: step
}, },
UI: ref.UI({ UI: ref.events,
stepName: "load-image",
stepID: ref.options.sequencerCounter++,
imageName: name
}),
draw: function() { draw: function() {
UI.onDraw(options.step);
if(arguments.length==1){ if(arguments.length==1){
this.output = CImage(arguments[0]); this.output = CImage(arguments[0]);
options.step.output = this.output;
UI.onComplete(options.step);
return true; return true;
} }
else if(arguments.length==2) { else if(arguments.length==2) {
this.output = CImage(arguments[0]); this.output = CImage(arguments[0]);
options.step.output = this.output;
arguments[1](); arguments[1]();
UI.onComplete(options.step);
return true; return true;
} }
return false; return false;
}, },
output: CImage(src)
}] }]
}; };
CImage(src, function(datauri) {
var output = makeImage(datauri);
ref.images[name] = image; ref.images[name] = image;
ref.images[name].steps[0].UI.onSetup(); var loadImageStep = ref.images[name].steps[0];
ref.images[name].steps[0].UI.onDraw(); loadImageStep.output = output;
ref.images[name].steps[0].UI.onComplete(image.steps[0].output.src); 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); return loadImage(name,src);

View File

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

View File

@@ -16,12 +16,12 @@
module.exports = function CropModule(options,UI) { module.exports = function CropModule(options,UI) {
options = options || {}; options = options || {};
options.title = "Crop Image"; options.title = "Crop Image";
UI.onSetup(); UI.onSetup(options.step);
var output var output
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
require('./Crop')(input,options,function(out,format){ require('./Crop')(input,options,function(out,format){
@@ -29,7 +29,8 @@
src: out, src: out,
format: format format: format
} }
UI.onComplete(out); options.step.output = out;
UI.onComplete(options.step);
callback(); callback();
}); });

View File

@@ -4,14 +4,14 @@
module.exports = function DoNothing(options,UI) { module.exports = function DoNothing(options,UI) {
options = options || {}; options = options || {};
options.title = "Decode QR Code"; options.title = "Decode QR Code";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
var jsQR = require('jsqr'); var jsQR = require('jsqr');
var getPixels = require('get-pixels'); var getPixels = require('get-pixels');
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
getPixels(input.src,function(err,pixels){ getPixels(input.src,function(err,pixels){
@@ -22,13 +22,15 @@ module.exports = function DoNothing(options,UI) {
step.output = input; step.output = input;
step.output.data = decoded; step.output.data = decoded;
callback(); callback();
UI.onComplete(input.src); options.step.output = input.src;
UI.onComplete(options.step);
}); });
} }
return { return {
options: options, options: options,
draw: draw, draw: draw,
output: output output: output,
UI: UI
} }
} }

View File

@@ -4,14 +4,17 @@
module.exports = function DoNothing(options,UI) { module.exports = function DoNothing(options,UI) {
options = options || {}; options = options || {};
options.title = "Do Nothing"; options.title = "Do Nothing";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
this.output = input; this.output = input;
options.step.output = this.output.src;
callback(); callback();
UI.onComplete(this.output.src); UI.onComplete(options.step);
} }
return { return {

View File

@@ -5,12 +5,12 @@ module.exports = function DoNothingPix(options,UI) {
options = options || {}; options = options || {};
options.title = "Do Nothing with pixels"; options.title = "Do Nothing with pixels";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -18,7 +18,8 @@ module.exports = function DoNothingPix(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype} step.output = {src:datauri,format:mimetype}
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,

View File

@@ -6,12 +6,12 @@ module.exports = function GreenChannel(options,UI) {
options = options || {}; options = options || {};
options.title = "Green channel only"; options.title = "Green channel only";
options.description = "Displays only the green channel of an image"; options.description = "Displays only the green channel of an image";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -19,7 +19,8 @@ module.exports = function GreenChannel(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,

View File

@@ -6,14 +6,14 @@ module.exports = function GreenChannel(options,UI) {
options = options || {}; options = options || {};
options.title = "Invert Colors"; options.title = "Invert Colors";
options.description = "Inverts the colors of the image"; options.description = "Inverts the colors of the image";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
//function setup() {} // optional //function setup() {} // optional
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -21,7 +21,8 @@ module.exports = function GreenChannel(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,

View File

@@ -5,12 +5,12 @@ module.exports = function NdviRed(options,UI) {
options = options || {}; options = options || {};
options.title = "NDVI for red-filtered cameras (blue is infrared)"; options.title = "NDVI for red-filtered cameras (blue is infrared)";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -20,7 +20,8 @@ module.exports = function NdviRed(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,

View File

@@ -2,12 +2,12 @@ module.exports = function SegmentedColormap(options,UI) {
options = options || {}; options = options || {};
options.title = "Segmented Colormap"; options.title = "Segmented Colormap";
UI.onSetup(); UI.onSetup(options.step);
var output; var output;
function draw(input,callback) { function draw(input,callback) {
UI.onDraw(); UI.onDraw(options.step);
const step = this; const step = this;
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -18,7 +18,8 @@ module.exports = function SegmentedColormap(options,UI) {
} }
function output(image,datauri,mimetype){ function output(image,datauri,mimetype){
step.output = {src:datauri,format:mimetype}; step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri); options.step.output = datauri;
UI.onComplete(options.step);
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,

View File

@@ -43,7 +43,7 @@ module.exports = function PixelManipulation(image, options) {
// but node modules and their documentation are essentially arcane on this point // but node modules and their documentation are essentially arcane on this point
var chunks = []; var chunks = [];
var totalLength = 0; var totalLength = 0;
var r = savePixels(pixels, options.format); var r = savePixels(pixels, options.format, {quality: 100});
r.on('data', function(chunk){ r.on('data', function(chunk){
totalLength += chunk.length; totalLength += chunk.length;

View File

@@ -17,15 +17,19 @@ test('loadImages/loadImage has a name generator.', function (t){
t.end(); t.end();
}); });
test('loadImages/loadImage returns a wrapper.', function (t){ test('loadImages/loadImage returns a wrapper in the callback.', function (t){
var returnval = sequencer.loadImage(red); sequencer.loadImage(red, function() {
var returnval = this;
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image2","Image scope is defined"); t.equal(returnval.images[0],"image2","Image scope is defined");
t.end(); t.end();
});
}); });
test('addSteps is two-way chainable.', function (t){ test('addSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).addSteps('invert'); sequencer.loadImage(red, function(){
var returnval = this;
this.addSteps('invert');
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image3","Image scope is defined"); 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.length,2,"Loaded image is affected");
@@ -33,6 +37,7 @@ test('addSteps is two-way chainable.', function (t){
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected"); 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.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
t.end(); t.end();
});
}); });
test('addSteps is two-way chainable without loadImages.', function (t){ test('addSteps is two-way chainable without loadImages.', function (t){
@@ -44,11 +49,14 @@ test('addSteps is two-way chainable without loadImages.', function (t){
}); });
test('removeSteps is two-way chainable.', function (t){ test('removeSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).addSteps('invert').removeSteps(1); sequencer.loadImage(red,function(){
var returnval = this;
this.addSteps('invert').removeSteps(1);
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
t.equal(returnval.images[0],"image4","Image scope is defined"); t.equal(returnval.images[0],"image4","Image scope is defined");
t.equal(sequencer.images.image4.steps.length,1); t.equal(sequencer.images.image4.steps.length,1);
t.end(); t.end();
});
}); });
test('removeSteps is two-way chainable without loadImages.', function (t){ test('removeSteps is two-way chainable without loadImages.', function (t){
@@ -59,12 +67,15 @@ test('removeSteps is two-way chainable without loadImages.', function (t){
}); });
test('insertSteps is two-way chainable.', function (t){ test('insertSteps is two-way chainable.', function (t){
var returnval = sequencer.loadImage(red).insertSteps(1,'invert'); sequencer.loadImage(red,function() {
var returnval = this;
this.insertSteps(1,'invert');
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned"); t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
t.equal(returnval.images[0],"image5","Image scope is defined"); t.equal(returnval.images[0],"image5","Image scope is defined");
t.equal(sequencer.images.image5.steps.length,2); t.equal(sequencer.images.image5.steps.length,2);
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted"); t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
t.end(); t.end();
});
}); });
test('insertSteps is two-way chainable without loadImages.', function (t){ test('insertSteps is two-way chainable without loadImages.', function (t){

View File

@@ -7,13 +7,21 @@ var test = require('tape');
require('../src/ImageSequencer.js'); require('../src/ImageSequencer.js');
//require image files as DataURLs so they can be tested alike on browser and Node.
var sequencer = ImageSequencer({ ui: false }); var sequencer = ImageSequencer({ ui: false });
var image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
var qr = require('../examples/IS-QR.js');
sequencer.loadImages(image);
var qr = require('../examples/IS-QR.js');
var test_png = require('../examples/test.png.js');
var test_gif = require('../examples/test.gif.js');
sequencer.loadImages(test_png);
sequencer.addSteps(['do-nothing-pix','invert','invert']); sequencer.addSteps(['do-nothing-pix','invert','invert']);
sequencer.run();
test("Preload", function(t) {
sequencer.run(function(){
t.end();
});
});
test("Inverted image isn't identical", function (t) { 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.notEqual(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[2].output.src);
@@ -26,12 +34,32 @@ test("Twice inverted image is identical to original image", function (t) {
}); });
test("Decode QR module works properly :: setup", function (t) { test("Decode QR module works properly :: setup", function (t) {
sequencer.loadImage(qr).addSteps('decode-qr').run(function(){ sequencer.loadImage(qr,function(){
this.addSteps('decode-qr').run(function(){
t.end(); t.end();
}); });
})
}); });
test("Decode QR module works properly :: teardown", function (t) { 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.equal("http://github.com/publiclab/image-sequencer",sequencer.images.image2.steps[1].output.data);
t.end(); t.end();
}); });
test("PixelManipulation works for PNG images", function (t) {
sequencer.loadImages(test_png,function(){
this.addSteps('invert').run(function(out){
t.equal(1,1)
t.end();
});
});
});
test("PixelManipulation works for GIF images", function (t) {
sequencer.loadImages(test_gif,function(){
this.addSteps('invert').run(function(out){
t.equal(1,1)
t.end();
});
});
});

View File

@@ -40,18 +40,22 @@ test('loadImages loads a DataURL image and creates a step.', function (t){
t.end(); t.end();
}); });
test('loadImages loads a PATH image and creates a step. (NodeJS)', function (t){ if(!sequencer.options.inBrowser)
if(sequencer.options.inBrowser){ test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){
t.equal("not applicable","not applicable","Not applicable for Browser"); sequencer.loadImage('URL','https://ccpandhare.github.io/image-sequencer/examples/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(); t.end();
} });
else { });
sequencer.loadImages(red);
if(!sequencer.options.inBrowser)
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t){
sequencer.loadImages('examples/red.jpg');
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created"); 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.equal(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
t.end(); t.end();
} });
});
test('loadImage works too.', function (t){ test('loadImage works too.', function (t){
sequencer.loadImage('test2',red); sequencer.loadImage('test2',red);