Merge Commit

This commit is contained in:
Chinmay Pandhare
2017-07-12 22:48:41 +05:30
23 changed files with 39124 additions and 643 deletions

File diff suppressed because one or more lines are too long

View File

@@ -2,12 +2,23 @@ language: node_js
node_js:
- '4'
- '5'
- '6'
- '7' # Node.js 7 is most stable version
env:
- CXX=g++-4.8
script: npm test
before_script:
- npm install grunt-cli -g # for "grunt build"
script:
- npm test
- grunt build
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- xvfb # for tape-run
install:
- export DISPLAY=':99.0' # for tape-run
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & # for tape-run
- npm install # for tape-run

259
README.md
View File

@@ -3,7 +3,7 @@ Image Sequencer
aka "Consequencer"
[![Build Status](https://travis-ci.org/jywarren/image-sequencer.svg?branch=master)](https://travis-ci.org/jywarren/image-sequencer)
[![Build Status](https://travis-ci.org/publiclab/image-sequencer.svg?branch=master)](https://travis-ci.org/publiclab/image-sequencer)
## Why
@@ -26,8 +26,31 @@ It is also for prototyping some other related ideas:
* [Basic example](https://jywarren.github.io/image-sequencer/)
* [NDVI example](https://jywarren.github.io/image-sequencer/examples/ndvi/) - related to [Infragram.org](http://infragram.org)
## Using the library:
## Quick Usage
Image Sequencer can be used to run modules on an HTML Image Element using the
`replaceImage` method. The method accepts two parameters - `selector` and `steps`.
`selector` is a CSS selector. If it matches multiple images, all images will be
modified. `steps` may be the name of a module or array of names of modules.
Note: Browser CORS Restrictions apply. Some browsers may not allow local images
form other folders, and throw a Security Error instead.
```js
sequencer.replaceImage(selector,steps,optional_options);
```
`optional_options` allows to pass additional arguments to the module itself.
For example:
```js
sequencer.replaceImage('#photo','invert');
sequencer.replaceImage('#photo',['invert','ndvi-red']);
```
## Classic Usage
### Initializing the Sequencer
@@ -37,11 +60,137 @@ The Image Sequencer Library exports a function ImageSequencer which initializes
var sequencer = ImageSequencer();
```
### Loading Images into the Sequencer
### Loading an Image into the Sequencer
Image Sequencer has an array of images which gets stored in `sequencer.images` in this case.
Images can be loaded into this array by the method `loadImages`.
loadImages accepts 1, 2, or 3 parameters.
The `loadImage` method is used to load an image into the sequencer. It accepts
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)
return value: **`sequencer`** (To allow method chaining)
### Adding steps to the image
The `addSteps` method is used to add steps on the image. One or more steps can
be added at a time. Each step is called a module.
```js
sequencer.addSteps(modules, optional_options);
```
If only one module is to be added, `modules` is simply the name of the module.
If multiple images are to be added, `modules` is an array of the names of modules
which are to be added, in that particular order.
optional_otions is just additional parameters, in object form, which you might
want to provide to the modules. It's an optional parameter.
return value: **`sequencer`** (To allow method chaining)
### Running the Sequencer
Once all steps are added, This method is used to generate the output of all these
modules.
```js
sequencer.run();
```
Additionally, an optional callback can be passed to this method.
```js
sequencer.run(function(out){
// this gets called back.
// "out" is the DataURL of the final image.
});
```
return value: **`sequencer`** (To allow method chaining)
### Removing a step from the sequencer
The `removeSteps` method is used to remove unwanted steps from the sequencer.
It accepts the index of the step as an input, or an array of the unwanted indices
if there are more than one.
For example, if the modules ['ndvi-red','crop','invert'] were added in this order,
and I wanted to remove 'crop' and 'invert', I can either do this:
```js
sequencer.removeSteps(2);
sequencer.removeSteps(3);
```
or:
```js
sequencer.removeSteps([2,3]);
```
return value: **`sequencer`** (To allow method chaining)
### Inserting a step in between the sequencer
The `insertSteps` method can be used to insert one or more steps at a given index
in the sequencer. It accepts the index where the module is to be inserted, name of
the module, and an optional options parameter. `index` is the index of the inserted
step. Only one step can be inserted at a time. `optional_options` plays the same
role it played in `addSteps`.
Indexes can be negative. Negative sign with an index means that counting will be
done in reverse order. If the index is out of bounds, the counting will wrap in
the original direction of counting. So, an `index` of -1 means that the module is
inserted at the end.
```js
sequencer.insertSteps(index,module_name,optional_options);
```
return value: **`sequencer`** (To allow method chaining)
## Method Chaining
Methods can be chained on the Image Sequencer:
* 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.
* If no name is provided to the image, a name will be generated for it. The name will
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.addSteps(['ndvi-red','invert']).run();
et cetra.
```
Invalid Chains:
```js
sequencer.addSteps('invert').run().addSteps('ndvi-red');
```
## Multiple Images
Image Sequencer is capable of handling multiple images at once.
### Initializing a sequencer with multiple images.
This is just like before.
```js
var sequencer = ImageSequencer();
```
### Loading Multiple Images into the Sequencer
Multiple images can be loaded by the method `loadImages`. Everything is the same,
except that now, a unique identification called `image_name` has to be provided
with each image. This is a string literal.
* 3/2 parameters :
```js
@@ -52,19 +201,22 @@ loadImages accepts 1, 2, or 3 parameters.
```js
sequencer.loadImages({
images: {
image_name_1: image_src,
image_name_2: image_src,
image1_name: image_src,
image2_name: image_src,
...
},
callback: optional_callback
});
```
### Adding Steps on Images
return value: **`sequencer`** (To allow method chaining)
After loading the image, we can add modules to the image using the addSteps method.
The options argument (object) is an optional parameter to pass in arguments to the module.
In all the following examples, `image_name` and `module_name` may be a string or an array of strings.
### Adding Steps on Multiple Images
The same method `addSteps` is used for this. There's just a slight obvious change
in the syntax that the image name has to be supplied too. `image_name` as well as
`module_name` in the following examples can be either strings or arrays of strings.
```js
sequencer.addSteps(image_name,module_name,optional_options);
@@ -80,53 +232,81 @@ All this can be passed in as JSON:
```js
sequencer.addSteps({
image_name: {name: module_name, o: optional_options},
image_name: {name: module_name, o: optional_options},
image1_name: {name: module_name, o: optional_options},
image2_name: {name: module_name, o: optional_options},
...
});
```
### Running the Sequencer
return value: **`sequencer`** (To allow method chaining)
After adding the steps, now we must generate output for each of the step via the `run` method.
The `run` method accepts parameters `image` and `from`.
`from` is the index from where the function starts generating output. By default, it will run across all the steps. (from = 1) If no image is specified, the sequencer will be run over all the images.
### Running a Sequencer with multiple images
The same `run` method can be used with a slight change in syntax.
The `run` method accepts parameters `image` and `from`. `from` is the index from
where the function starts generating output. By default, it will run across all
the steps. (from = 1) If no image is specified, the sequencer will be run over **all
the images**. `image_name` may be an array of image names.
```js
sequencer.run(); //All images from first step
```
```js
sequencer.run(image,from); //Image 'image' from 'from'
sequencer.run(image_name,from); //Image 'image' from 'from'
```
image may either be an array or a string.
An optional callback may also be passed.
### Removing Steps from an Image
Steps can be removed using the `removeSteps` method. It accepts `image` and `index` as parameters.
Either, both, or none of them can be an array. JSON input is also accepted.
The `run` method also accepts an optional callback just like before:
```js
sequencer.removeSteps("image",[steps]);
sequencer.run(image_name,from,function(out){
// This gets called back.
// "out" is the DataURL of final image.
});
```
```js
sequencer.removeSteps("image",step);
```
JSON Input is also acceptable.
```js
sequencer.removeSteps({
image: [steps],
image: [steps],
sequencer.run({
image1_name: from,
image2_name: from,
...
});
```
return value: **`sequencer`** (To allow method chaining)
### Removing Steps from an Image
Similarly, `removeSteps` can also accept an `image_name` parameter. Either, both,
or none of `image_name` and `steps` them may be an array. JSON input is also acceptable.
```js
sequencer.removeSteps("image_name",[steps]);
```
```js
sequencer.removeSteps("image_name",step);
```
```js
sequencer.removeSteps({
image1_name: [steps],
image2_name: [steps],
...
});
```
return value: **`sequencer`** (To allow method chaining)
### Inserting steps on an image
Steps can be inserted using the `insertSteps` method. It accepts `image`, `index`, `module_name` and `optional_options` as parameters. `image` may be an array. `optional_options` is an object. The rest are literals. JSON Input is supported too. If no image is provided, Steps will be inserted on all images. Indexes can be negative. Negative sign with an index means that counting will be done in reverse order. If the index is out of bounds, the counting will wrap in the original direction of counting.
The `insertSteps` method can also accept an `image_name` parameter. `image_name`
may be an array. Everything else remains the same. JSON Inout is acceptable too.
```js
sequencer.insertSteps("image",index,"module_name",o);
```
@@ -142,6 +322,7 @@ sequencer.insertSteps({
]
});
```
return value: **`sequencer`** (To allow method chaining)
## Contributing
@@ -214,16 +395,16 @@ Notes on development next steps:
### Modularization
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
* [ ] ability to start running at any point -- already works?
* [ ] commandline runnability?
* [ ] Make available as browserified OR `require()` includable...
* [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
* [ ] remove step
* [x] remove step
### Testing
@@ -237,7 +418,7 @@ Notes on development next steps:
### Bugs
* [ ] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
* we should make defaults a config of the first module
****

View File

@@ -1,6 +1,6 @@
/* https://github.com/theleagueof/league-spartan */
@font-face {
/*@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'),
@@ -10,7 +10,7 @@
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;

1409
dist/image-sequencer.js vendored

File diff suppressed because one or more lines are too long

BIN
examples/cyan.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

63
examples/replace.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Replace Image Demo | 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>
</head>
<body>
<div class="header">
<h1>Image Sequencer</h1>
<h3>
<a href="https://github.com/publiclab/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" style="display:flex;justify-content:center">
<img src="replace.jpg" id="pencils" style="cursor:pointer">
</div>
</div>
<div class="mod-new-panel" style="display:flex;justify-content:center">
<p class="lead" style="text-align:center">
Click on the image above to invert it.<br>
(This may take a few seconds)<br>
Syntax:</p>
</div>
<div class="log">
<h4>var sequencer = new ImageSequencer();</h4><br>
<h4>sequencer.replaceImage('#pencils','invert');</h4>
</div>
<script>
var sequencer = ImageSequencer();
document.querySelector('#pencils').onclick = function() {
sequencer.replaceImage('#pencils','invert');
}
</script>
</body>
</html>

BIN
examples/replace.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Image Sequencer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@@ -4,7 +4,7 @@
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
"main": "dist/image-sequencer.js",
"scripts": {
"test": "tape test/*.js"
"test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\""
},
"repository": {
"type": "git",
@@ -39,7 +39,9 @@
"matchdep": "^0.3.0",
"plotly.js": "~1.21.2",
"save-pixels": "~2.3.4",
"tape": "^3.5.0"
"tap-spec": "^4.1.1",
"tape": ">=4.7.0",
"tape-run": "^3.0.0"
},
"homepage": "https://github.com/publiclab/image-sequencer"
}

View File

@@ -1,9 +1,9 @@
function AddStep(ref, image, name, o) {
function addStep(image, name, o_) {
ref.clog('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
ref.log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
o = {};
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;

View File

@@ -24,7 +24,7 @@ function copy(a) {
}
function formatInput(args,format,images) {
images = images || [];
images = [];
for (image in this.images) {
images.push(image);
}
@@ -39,7 +39,7 @@ function formatInput(args,format,images) {
else if (format == "r")
format = ['o_string_a', 'o_number'];
else if (format == "l")
format = ['string','string','o_function'];
format = ['o_string','string','o_function'];
/*
formats:
@@ -87,6 +87,14 @@ function formatInput(args,format,images) {
args.splice(0,0,copy(images));
}
}
else if (format[0] == "o_string" && format_i == "l" && args.length == 2) {
if (typeof(args[0]) == "string") {
identifier = "image";
number = 1;
while (this.images.hasOwnProperty(identifier+number)) number++;
args.splice(0,0,identifier+number);
}
}
if(args.length == format.length) {
for (i in format) {
@@ -145,6 +153,11 @@ function formatInput(args,format,images) {
}
}
if(format_i == "l") {
json_q.loadedimages = [];
for (i in json_q.images) json_q.loadedimages.push(i);
}
return json_q;
}

View File

@@ -1,5 +1,5 @@
if (typeof window !== 'undefined') {window.$ = window.jQuery = require('jquery'); isBrowser = true}
else {window = global; var isBrowser = false}
else {var isBrowser = false}
ImageSequencer = function ImageSequencer(options) {
@@ -12,7 +12,7 @@ ImageSequencer = function ImageSequencer(options) {
return Object.prototype.toString.call(object).split(" ")[1].slice(0,-1)
}
function clog(color,msg) {
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);
@@ -42,27 +42,32 @@ ImageSequencer = function ImageSequencer(options) {
steps = [],
modules = require('./Modules'),
images = {},
log = [];
inputlog = [];
// if in browser, prompt for an image
// if (options.imageSelect || options.inBrowser) addStep('image-select');
// else if (options.imageUrl) loadImage(imageUrl);
function addSteps(){
args = [];
json_q = {};
for(arg in arguments){args.push(copy(arguments[arg]));}
json_q = formatInput.call(this,args,"+");
log.push({method:"addSteps", json_q:copy(json_q)});
for (i in json_q)
for (j in json_q[i])
require("./AddStep")(this,i,json_q[i][j].name,json_q[i][j].o);
}
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
args = (this.name == "ImageSequencer")?[]:[this.images];
json_q = {};
for(arg in arguments){args.push(copy(arguments[arg]));}
json_q = formatInput.call(this_,args,"+");
inputlog.push({method:"addSteps", json_q:copy(json_q)});
for (i in json_q)
for (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) {
clog('\x1b[31m%s\x1b[0m',"Removing "+index+" from "+image);
log('\x1b[31m%s\x1b[0m',"Removing "+index+" from "+image);
images[image].steps.splice(index,1);
}
//tell the UI a step has been removed
@@ -70,10 +75,12 @@ ImageSequencer = function ImageSequencer(options) {
function removeSteps(image,index) {
run = {};
args = [];
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
args = (this.name == "ImageSequencer")?[]:[this.images];
for(arg in arguments) args.push(copy(arguments[arg]));
json_q = formatInput.call(this,args,"-");
log.push({method:"removeSteps", json_q:copy(json_q)});
json_q = formatInput.call(this_,args,"-");
inputlog.push({method:"removeSteps", json_q:copy(json_q)});
for (img in json_q) {
indices = json_q[img].sort(function(a,b){return b-a});
@@ -82,77 +89,92 @@ ImageSequencer = function ImageSequencer(options) {
removeStep(img,indices[i]);
}
// this.run(run); // This is creating problems
return this;
}
function insertSteps(image, index, name, o) {
run = {};
this_ = this;
args = [];
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
args = (this.name == "ImageSequencer")?[]:[this.images];
for (arg in arguments) args.push(arguments[arg]);
json_q = formatInput.call(this,args,"^");
log.push({method:"insertSteps", json_q:copy(json_q)});
json_q = formatInput.call(this_,args,"^");
inputlog.push({method:"insertSteps", json_q:copy(json_q)});
for (img in json_q) {
var details = json_q[img];
details = details.sort(function(a,b){return b.index-a.index});
for (i in details)
require("./InsertStep")(this,img,details[i].index,details[i].name,details[i].o);
// run[img] = details[details.length-1].index;
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(t_image,t_from) {
clog('\x1b[32m%s\x1b[0m',"Running the Sequencer!");
this_ = this;
args = [];
log('\x1b[32m%s\x1b[0m',"Running the Sequencer!");
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
args = (this.name == "ImageSequencer")?[]:[this.images];
for (var arg in arguments) args.push(copy(arguments[arg]));
callback = function() {};
for (var arg in args)
if(objTypeOf(args[arg]) == "Function")
callback = args.splice(arg,1)[0];
json_q = formatInput.call(this,args,"r");
json_q = formatInput.call(this_,args,"r");
require('./Run')(this, json_q, callback);
require('./Run')(this_, json_q, callback);
return true;
}
function loadImages() {
args = [];
for (arg in arguments) args.push(copy(arguments[arg]));
json_q = formatInput.call(this,args,"l");
json_q_push = copy(json_q);
delete json_q_push.callback;
log.push({method:"loadImages", json_q:json_q_push});
inputlog.push({method:"loadImages", json_q:copy(json_q)});
loadedimages = this.copy(json_q.loadedimages);
for (i in json_q.images)
require('./LoadImage')(this,i,json_q.images[i])
if (json_q.callback) json_q.callback();
json_q.callback();
return {
name: "ImageSequencer Wrapper",
sequencer: this,
addSteps: this.addSteps,
removeSteps: this.removeSteps,
insertSteps: this.insertSteps,
run: this.run,
images: loadedimages
};
}
function runLog() {
for(i in sequencer.log)
eval("sequencer."+sequencer.log[i].method).call(sequencer,sequencer.log[i].json_q);
return true
function replaceImage(selector,steps,options) {
options = options || {};
return require('./ReplaceImage')(this,selector,steps);
}
return {
name: "ImageSequencer",
options: options,
loadImages: loadImages,
loadImage: loadImages,
addSteps: addSteps,
removeSteps: removeSteps,
insertSteps: insertSteps,
replaceImage: replaceImage,
run: run,
log: log,
inputlog: inputlog,
modules: modules,
images: images,
ui: options.ui,
clog: clog,
log: log,
objTypeOf: objTypeOf,
copy: copy,
runLog: runLog
copy: copy
}
}

View File

@@ -1,9 +1,9 @@
function InsertStep(ref, image, index, name, o) {
function insertStep(image, index, name, o) {
ref.clog('\x1b[36m%s\x1b[0m','inserting step \"' + name + '\" to \"' + image + '\" at \"'+index+'\".');
function insertStep(image, index, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','inserting step \"' + name + '\" to \"' + image + '\" at \"'+index+'\".');
o = o || {};
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;

View File

@@ -19,7 +19,12 @@ function LoadImage(ref, name, src) {
},
draw: function() {
if(arguments.length==1){
this.outputData = CImage(arguments[0]);
this.output = CImage(arguments[0]);
return true;
}
else if(arguments.length==2) {
this.output = CImage(arguments[0]);
arguments[1]();
return true;
}
return false;

View File

@@ -6,5 +6,6 @@ module.exports = {
'green-channel': require('./modules/GreenChannel'),
'ndvi-red': require('./modules/NdviRed'),
'do-nothing-pix': require('./modules/DoNothingPix'),
'invert': require('./modules/Invert')
'invert': require('./modules/Invert'),
'crop': require('./modules/Crop')
}

35
src/ReplaceImage.js Normal file
View File

@@ -0,0 +1,35 @@
function ReplaceImage(ref,selector,steps,options) {
if(!ref.options.inBrowser) return false; // This isn't for Node.js
this_ = ref;
var input = document.querySelectorAll(selector);
var images = [];
for (i = 0; i < input.length; i++)
if (input[i] instanceof HTMLImageElement) images.push(input[i]);
for (i in images) {
the_image = images[i];
var url = images[i].src;
var ext = url.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);
var base64 = btoa(raw);
var dataURL="data:image/"+ext+";base64," + base64;
make(dataURL);
};
if(url.substr(0,11).toLowerCase()!="data:image/") xmlHTTP.send();
else make(url);
function make(url) {
this_.loadImage('default',url).addSteps('default',steps).run(function(out){
the_image.src = out;
});
}
}
}
module.exports = ReplaceImage;

View File

@@ -1,8 +1,14 @@
function Run(ref, json_q, callback) {
function drawStep(drawarray,pos) {
if(pos==drawarray.length) if(ref.objTypeOf(callback)=='Function') callback();
if(pos>=drawarray.length) return true;
if(pos==drawarray.length) {
image = drawarray[pos-1].image;
if(ref.objTypeOf(callback)=='Function'){
steps = ref.images[image].steps;
out = steps[steps.length-1].output.src;
callback(out);
return true;
}
}
image = drawarray[pos].image;
i = drawarray[pos].i;
input = ref.images[image].steps[i-1].output;
@@ -25,7 +31,7 @@ function Run(ref, json_q, callback) {
for (image in json_q) {
if (json_q[image]==0 && ref.images[image].steps.length==1)
delete json_q[image];
else json_q[image]++;
else if (json_q[image]==0) json_q[image]++;
}
for (image in json_q) {
prevstep = ref.images[image].steps[json_q[image]-1];
@@ -36,6 +42,6 @@ function Run(ref, json_q, callback) {
return json_q;
}
json_q = filter(json_q);
drawSteps(json_q);
return drawSteps(json_q);
}
module.exports = Run;

View File

@@ -13,18 +13,20 @@
* y = options.y
* y = options.y + options.h
*/
module.exports = function Crop(options){
module.exports = function Crop(options) {
options = options || {};
options.title = "Crop Image";
options.format = options.format || "png";
options.title = "Do Nothing";
this_ = this;
var output
var getPixels = require("get-pixels"),
savePixels = require("save-pixels"),
base64 = require('base64-stream');
function draw(image) {
var getPixels = require("get-pixels"),
savePixels = require("save-pixels"),
base64 = require('base64-stream');
function draw(input,callback) {
getPixels(image.src,function(err,pixels){
const this_ = this;
getPixels(input.src,function(err,pixels){
var newdata = [];
var ox = options.x || 0;
var oy = options.y || 0;
@@ -39,23 +41,23 @@
pixels.shape = [w,h,4];
pixels.stride[1] = 4*w;
var buffer = base64.encode();
savePixels(pixels, options.format)
.on('end', function() {
var img = new Image();
img.src = 'data:image/' + options.format + ';base64,' + buffer.read().toString();
if (options.output) options.output(img);
}).pipe(buffer);
options.format = "jpeg";
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;
this_.output = {src:datauri,format:options.format};
callback();
});
});
}
return {
options: options,
draw: draw
draw: draw,
output: output
}
}

View File

@@ -9,28 +9,25 @@ module.exports = function ImageThreshold(options) {
var image;
function draw(inputImage) {
$(inputImage).load(function(){
var canvas = document.createElement('canvas');
canvas.width = inputImage.naturalWidth;
canvas.height = inputImage.naturalHeight;
var context = canvas.getContext('2d');
context.drawImage(inputImage, 0, 0);
var canvas = document.createElement('canvas');
canvas.width = inputImage.naturalWidth;
canvas.height = inputImage.naturalHeight;
var context = canvas.getContext('2d');
context.drawImage(inputImage, 0, 0 );
var imageData = context.getImageData(0, 0, inputImage.naturalWidth, inputImage.naturalHeight);
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
var imageThreshold = require('image-filter-threshold');
var imageFilterCore = require('image-filter-core');
var imageThreshold = require('image-filter-threshold');
var imageFilterCore = require('image-filter-core');
var result = imageThreshold({
data: imageData,
threshold: options.threshold
}).then(function (imageData) {
image = new Image();
image.onload = function onLoad() {
if (options.output) options.output(image);
}
image.src = imageFilterCore.convertImageDataToCanvasURL(imageData);
});
var result = imageThreshold({
data: imageData,
threshold: options.threshold
}).then(function (imageData) {
image = new Image();
image.onload = function onLoad() {
if (options.output) options.output(image);
}
image.src = imageFilterCore.convertImageDataToCanvasURL(imageData);
});
}

76
test/chain.js Normal file
View File

@@ -0,0 +1,76 @@
'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();
});

View File

@@ -9,20 +9,17 @@ 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({images:{
test1: image,
test2: image
}, callback:function(){}});
sequencer.loadImages(image);
sequencer.addSteps("test1", ['do-nothing-pix','invert','invert']);
sequencer.addSteps(['do-nothing-pix','invert','invert']);
sequencer.run();
test("Inverted image isn't identical", function (t) {
t.notEqual(sequencer.images.test1.steps[1].output.src, sequencer.images.test1.steps[2].output.src);
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.test1.steps[1].output.src, sequencer.images.test1.steps[3].output.src);
t.equal(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[3].output.src);
t.end();
});

View File

@@ -9,102 +9,132 @@ var test = require('tape');
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('Image Sequencer has tests', function (t) {
t.equal(true, true);
t.equal(true, true, "Initial Test");
t.end();
});
test('loadImages loads an image and creates a step.', function (t){
sequencer.loadImages('test','examples/red.jpg');
t.equal(sequencer.images.test.steps.length, 1, "It Does!");
test('loadImages loads a DataURL image and creates a step.', function (t){
sequencer.loadImages('test',red);
t.equal(sequencer.images.test.steps.length, 1, "Initial Step Created");
t.equal(typeof(sequencer.images.test.steps[0].output.src), "string", "Initial output exists");
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);
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);
t.equal(sequencer.images.test2.steps.length, 1, "Initial Step Created");
t.equal(typeof(sequencer.images.test2.steps[0].output.src), "string", "Initial output exists");
t.end();
});
test('addSteps("image","name") adds a step', function (t) {
sequencer.addSteps('test','do-nothing');
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 2, "It Does!")
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.end();
});
test('addSteps("name") adds a step', function (t) {
sequencer.addSteps('do-nothing');
t.equal(sequencer.images.test.steps[2].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 3, "It Does!")
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.end();
});
test('addSteps(["name"]) adds a step', function (t) {
sequencer.addSteps(['do-nothing','do-nothing-pix']);
t.equal(sequencer.images.test.steps[3].options.name, "do-nothing");
t.equal(sequencer.images.test.steps[4].options.name, "do-nothing-pix");
t.equal(sequencer.images.test.steps.length, 5, "It Does!")
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.end();
});
test('addSteps("name",o) adds a step', function (t) {
sequencer.addSteps('do-nothing',{});
t.equal(sequencer.images.test.steps[5].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 6, "It Does!")
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.end();
});
test('addSteps("image","name",o) adds a step', function (t) {
sequencer.addSteps('test','do-nothing',{});
t.equal(sequencer.images.test.steps[6].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 7, "It Does!")
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.end();
});
test('removeSteps("image",position) removes a step', function (t) {
sequencer.removeSteps('test',1);
t.equal(sequencer.images.test.steps.length, 6, "It Does!");
t.equal(sequencer.images.test.steps.length, 6, "Length of steps reduced");
t.end();
});
test('removeSteps({image: [positions]}) removes steps', function (t) {
sequencer.removeSteps('test',[1,2]);
t.equal(sequencer.images.test.steps.length, 4, "It Does!");
t.equal(sequencer.images.test.steps.length, 4, "Length of steps reduced");
t.end();
});
test('removeSteps(position) removes steps', function (t) {
sequencer.removeSteps([1,2]);
t.equal(sequencer.images.test.steps.length, 2, "It Does!");
t.equal(sequencer.images.test.steps.length, 2, "Length of steps reduced");
t.end();
});
test('insertSteps("image",position,"module",options) inserts a step', function (t) {
sequencer.insertSteps('test',1,'do-nothing',{});
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 3, "It Does!");
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.end();
});
test('insertSteps("image",position,"module") inserts a step', function (t) {
sequencer.insertSteps('test',1,'do-nothing');
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 4, "It Does!");
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.end();
});
test('insertSteps(position,"module") inserts a step', function (t) {
sequencer.insertSteps(1,'do-nothing');
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 5, "It Does!");
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.end();
});
test('insertSteps({image: {index: index, name: "module", o: options} }) inserts a step', function (t) {
sequencer.insertSteps({test: {index:1, name:'do-nothing', o:{} } });
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing");
t.equal(sequencer.images.test.steps.length, 6, "It Does!");
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.end();
});
test('run() runs the sequencer', function (t) {
sequencer.run();
t.equal(typeof(sequencer.images.test.steps[sequencer.images.test.steps.length - 1].output), "object", "It Does!");
test('run() runs the sequencer and returns output to callback', function (t) {
sequencer.run('test',function(out){
t.equal(typeof(sequencer.images.test.steps[sequencer.images.test.steps.length - 1].output), "object", "Output is Generated");
t.equal(out,sequencer.images.test.steps[sequencer.images.test.steps.length - 1].output.src, "Output callback works");
});
t.end();
});
test('replaceImage returns false in NodeJS', function (t) {
var returnvalue = (sequencer.options.inBrowser)?false:sequencer.replaceImage("#selector","test");
t.equal(returnvalue,false,"It does.");
t.end();
});