diff --git a/README.md b/README.md index afbc9c34..2465ac97 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,26 @@ a name and an image. The method also accepts an optional callback. ```js sequencer.loadImage(image_src,optional_callback); ``` -On `Node.js` the `image_src` may be a DataURI or a local path. On browsers, it -must be a DatURI (or 'selector to image' -- Work in Progress) +On `Node.js` the `image_src` may be a DataURI or a local path or a URL. -return value: **`sequencer`** (To allow method chaining) +On browsers, it may be a DatURI, a local image or a URL (Unless this violates +CORS Restrictions). To sum up, these are accepted: +* Images in the same domain (or directory - for a local implementation) +* CORS-Proof images in another domain. +* DataURLs +return value: **none** (A callback should be used to ensure the image gets loaded) +The callback is called within the scope of a the sequencer. For example: +(addSteps is defined later) + +```js +sequencer.loadImage('SRC',function(){ + this.addSteps('module-name'); +}); +``` + +The `this` refers to all the images added in the parent `loadImages` function only. +In this case, only `'SRC'`. ### Adding steps to the image @@ -164,6 +179,7 @@ return value: **`sequencer`** (To allow method chaining) ## Method Chaining Methods can be chained on the Image Sequencer: +* loadImage()/loadImages() can only terminate a chain. * run() can not be in the middle of the chain. * If the chain starts with loadImage() or loadImages(), the following methods are applied only to the newly loaded images. @@ -172,9 +188,11 @@ be of the form "image". For ex: "image1", "image2", "image3", etc. Valid Chains: ```js -sequencer.loadImage('red').addSteps('invert').run(function(out){ - //do something with otuput. -}); +sequencer.loadImage('red',function(){ + this.addSteps('invert').run(function(out){ + //do something with ouptut. + }); +}) sequencer.addSteps(['ndvi-red','invert']).run(); et cetra. ``` @@ -217,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 diff --git a/dist/image-sequencer.js b/dist/image-sequencer.js index f9316e4a..9af5ec20 100644 --- a/dist/image-sequencer.js +++ b/dist/image-sequencer.js @@ -34711,17 +34711,15 @@ ImageSequencer = function ImageSequencer(options) { function loadImages() { var args = []; + var sequencer = this; for (var arg in arguments) args.push(copy(arguments[arg])); var json_q = formatInput.call(this,args,"l"); inputlog.push({method:"loadImages", json_q:copy(json_q)}); var loadedimages = this.copy(json_q.loadedimages); +// require('./LoadImage')(this,i,json_q.images[i]); - for (var i in json_q.images) - require('./LoadImage')(this,i,json_q.images[i]) - - json_q.callback(); - return { + var ret = { name: "ImageSequencer Wrapper", sequencer: this, addSteps: this.addSteps, @@ -34732,6 +34730,19 @@ ImageSequencer = function ImageSequencer(options) { setUI: this.setUI, images: loadedimages }; + + function load(i) { + if(i==loadedimages.length) { + json_q.callback.call(ret); + return; + } + var img = loadedimages[i]; + require('./LoadImage')(sequencer,img,json_q.images[img],function(){ + load(++i); + }); + } + + load(0); } function replaceImage(selector,steps,options) { @@ -34803,15 +34814,50 @@ function InsertStep(ref, image, index, name, o) { module.exports = InsertStep; },{}],115:[function(require,module,exports){ -function LoadImage(ref, name, src) { - function CImage(src) { - var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src); +function LoadImage(ref, name, src, main_callback) { + function makeImage(datauri) { var image = { src: datauri, format: datauri.split(':')[1].split(';')[0].split('/')[1] } return image; } + function CImage(src, callback) { + var datauri; + if (!!src.match(/^data:/i)) { + datauri = src; + callback(datauri); + } + else if (!ref.options.inBrowser && !!src.match(/^https?:\/\//i)) { + require( src.match(/^(https?):\/\//i)[1] ).get(src,function(res){ + var data = ''; + var contentType = res.headers['content-type']; + res.setEncoding('base64'); + res.on('data',function(chunk) {data += chunk;}); + res.on('end',function() { + callback("data:"+contentType+";base64,"+data); + }); + }); + } + else if (ref.options.inBrowser) { + var ext = src.split('.').pop(); + var image = document.createElement('img'); + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + image.onload = function() { + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + context.drawImage(image,0,0); + datauri = canvas.toDataURL(ext); + callback(datauri); + } + image.src = src; + } + else { + datauri = require('urify')(src); + callback(datauri); + } + } function loadImage(name, src) { var step = { @@ -34849,15 +34895,21 @@ function LoadImage(ref, name, src) { } return false; }, - output: CImage(src) }] }; - ref.images[name] = image; - loadImageStep = ref.images[name].steps[0]; - 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); + CImage(src, function(datauri) { + var output = makeImage(datauri); + ref.images[name] = image; + var loadImageStep = ref.images[name].steps[0]; + loadImageStep.output = output; + loadImageStep.options.step.output = loadImageStep.output.src; + loadImageStep.UI.onSetup(loadImageStep.options.step); + loadImageStep.UI.onDraw(loadImageStep.options.step); + loadImageStep.UI.onComplete(loadImageStep.options.step); + + main_callback(); + return true; + }); } return loadImage(name,src); @@ -35431,7 +35483,7 @@ module.exports = function PixelManipulation(image, options) { // but node modules and their documentation are essentially arcane on this point var chunks = []; var totalLength = 0; - var r = savePixels(pixels, options.format); + var r = savePixels(pixels, options.format, {quality: 100}); r.on('data', function(chunk){ totalLength += chunk.length; diff --git a/examples/red.png b/examples/red.png new file mode 100644 index 00000000..239e2c5a Binary files /dev/null and b/examples/red.png differ diff --git a/src/ImageSequencer.js b/src/ImageSequencer.js index 204896c6..8decaa40 100644 --- a/src/ImageSequencer.js +++ b/src/ImageSequencer.js @@ -133,17 +133,15 @@ ImageSequencer = function ImageSequencer(options) { function loadImages() { var args = []; + var sequencer = this; for (var arg in arguments) args.push(copy(arguments[arg])); var json_q = formatInput.call(this,args,"l"); inputlog.push({method:"loadImages", json_q:copy(json_q)}); var loadedimages = this.copy(json_q.loadedimages); +// require('./LoadImage')(this,i,json_q.images[i]); - for (var i in json_q.images) - require('./LoadImage')(this,i,json_q.images[i]) - - json_q.callback(); - return { + var ret = { name: "ImageSequencer Wrapper", sequencer: this, addSteps: this.addSteps, @@ -154,6 +152,19 @@ ImageSequencer = function ImageSequencer(options) { setUI: this.setUI, images: loadedimages }; + + function load(i) { + if(i==loadedimages.length) { + json_q.callback.call(ret); + return; + } + var img = loadedimages[i]; + require('./LoadImage')(sequencer,img,json_q.images[img],function(){ + load(++i); + }); + } + + load(0); } function replaceImage(selector,steps,options) { diff --git a/src/LoadImage.js b/src/LoadImage.js index 2c679313..8759e828 100644 --- a/src/LoadImage.js +++ b/src/LoadImage.js @@ -1,12 +1,47 @@ -function LoadImage(ref, name, src) { - function CImage(src) { - var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src); +function LoadImage(ref, name, src, main_callback) { + function makeImage(datauri) { var image = { src: datauri, format: datauri.split(':')[1].split(';')[0].split('/')[1] } return image; } + function CImage(src, callback) { + var datauri; + if (!!src.match(/^data:/i)) { + datauri = src; + callback(datauri); + } + else if (!ref.options.inBrowser && !!src.match(/^https?:\/\//i)) { + require( src.match(/^(https?):\/\//i)[1] ).get(src,function(res){ + var data = ''; + var contentType = res.headers['content-type']; + res.setEncoding('base64'); + res.on('data',function(chunk) {data += chunk;}); + res.on('end',function() { + callback("data:"+contentType+";base64,"+data); + }); + }); + } + else if (ref.options.inBrowser) { + var ext = src.split('.').pop(); + var image = document.createElement('img'); + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + image.onload = function() { + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + context.drawImage(image,0,0); + datauri = canvas.toDataURL(ext); + callback(datauri); + } + image.src = src; + } + else { + datauri = require('urify')(src); + callback(datauri); + } + } function loadImage(name, src) { var step = { @@ -44,15 +79,21 @@ function LoadImage(ref, name, src) { } return false; }, - output: CImage(src) }] }; - ref.images[name] = image; - loadImageStep = ref.images[name].steps[0]; - 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); + CImage(src, function(datauri) { + var output = makeImage(datauri); + ref.images[name] = image; + var loadImageStep = ref.images[name].steps[0]; + loadImageStep.output = output; + loadImageStep.options.step.output = loadImageStep.output.src; + loadImageStep.UI.onSetup(loadImageStep.options.step); + loadImageStep.UI.onDraw(loadImageStep.options.step); + loadImageStep.UI.onComplete(loadImageStep.options.step); + + main_callback(); + return true; + }); } return loadImage(name,src); diff --git a/src/modules/_nomodule/PixelManipulation.js b/src/modules/_nomodule/PixelManipulation.js index efb25863..f403b346 100644 --- a/src/modules/_nomodule/PixelManipulation.js +++ b/src/modules/_nomodule/PixelManipulation.js @@ -43,7 +43,7 @@ module.exports = function PixelManipulation(image, options) { // but node modules and their documentation are essentially arcane on this point var chunks = []; var totalLength = 0; - var r = savePixels(pixels, options.format); + var r = savePixels(pixels, options.format, {quality: 100}); r.on('data', function(chunk){ totalLength += chunk.length; diff --git a/test.js b/test.js deleted file mode 100644 index bf868390..00000000 --- a/test.js +++ /dev/null @@ -1,4 +0,0 @@ -require('./src/ImageSequencer'); -sequencer = ImageSequencer(); -sequencer.loadImages('examples/red.jpg'); -sequencer.addSteps('do-nothing'); diff --git a/test/chain.js b/test/chain.js index 6a77fca3..49916582 100644 --- a/test/chain.js +++ b/test/chain.js @@ -17,22 +17,27 @@ test('loadImages/loadImage has a name generator.', function (t){ t.end(); }); -test('loadImages/loadImage returns a wrapper.', function (t){ - var returnval = sequencer.loadImage(red); - t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); - t.equal(returnval.images[0],"image2","Image scope is defined"); - t.end(); +test('loadImages/loadImage returns a wrapper in the callback.', function (t){ + sequencer.loadImage(red, function() { + var returnval = this; + t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); + t.equal(returnval.images[0],"image2","Image scope is defined"); + t.end(); + }); }); test('addSteps is two-way chainable.', function (t){ - var returnval = sequencer.loadImage(red).addSteps('invert'); - t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); - t.equal(returnval.images[0],"image3","Image scope is defined"); - t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected"); - t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added"); - t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected"); - t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected"); - t.end(); + sequencer.loadImage(red, function(){ + var returnval = this; + this.addSteps('invert'); + t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); + t.equal(returnval.images[0],"image3","Image scope is defined"); + t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected"); + t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added"); + t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected"); + t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected"); + t.end(); + }); }); test('addSteps is two-way chainable without loadImages.', function (t){ @@ -44,11 +49,14 @@ test('addSteps is two-way chainable without loadImages.', function (t){ }); test('removeSteps is two-way chainable.', function (t){ - var returnval = sequencer.loadImage(red).addSteps('invert').removeSteps(1); - t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); - t.equal(returnval.images[0],"image4","Image scope is defined"); - t.equal(sequencer.images.image4.steps.length,1); - t.end(); + sequencer.loadImage(red,function(){ + var returnval = this; + this.addSteps('invert').removeSteps(1); + t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned"); + t.equal(returnval.images[0],"image4","Image scope is defined"); + t.equal(sequencer.images.image4.steps.length,1); + t.end(); + }); }); test('removeSteps is two-way chainable without loadImages.', function (t){ @@ -59,12 +67,15 @@ test('removeSteps is two-way chainable without loadImages.', function (t){ }); test('insertSteps is two-way chainable.', function (t){ - var returnval = sequencer.loadImage(red).insertSteps(1,'invert'); - t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned"); - t.equal(returnval.images[0],"image5","Image scope is defined"); - t.equal(sequencer.images.image5.steps.length,2); - t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted"); - t.end(); + sequencer.loadImage(red,function() { + var returnval = this; + this.insertSteps(1,'invert'); + t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned"); + t.equal(returnval.images[0],"image5","Image scope is defined"); + t.equal(sequencer.images.image5.steps.length,2); + t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted"); + t.end(); + }); }); test('insertSteps is two-way chainable without loadImages.', function (t){ diff --git a/test/image-manip.js b/test/image-manip.js index 2dbb3318..14d4a292 100644 --- a/test/image-manip.js +++ b/test/image-manip.js @@ -10,13 +10,17 @@ 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 image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z"; var test_png = require('../examples/test.png.js'); var test_gif = require('../examples/test.gif.js'); -sequencer.loadImages(image); +sequencer.loadImages(test_png); 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) { t.notEqual(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[2].output.src); @@ -29,15 +33,19 @@ test("Twice inverted image is identical to original image", function (t) { }); test("PixelManipulation works for PNG images", function (t) { - sequencer.loadImages(test_png).addSteps('invert').run(function(out){ - t.equal(1,1) - t.end(); + 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).addSteps('invert').run(function(out){ - t.equal(1,1) - t.end(); + sequencer.loadImages(test_gif,function(){ + this.addSteps('invert').run(function(out){ + t.equal(1,1) + t.end(); + }); }); }); diff --git a/test/image-sequencer.js b/test/image-sequencer.js index 3c52423b..651e5af3 100644 --- a/test/image-sequencer.js +++ b/test/image-sequencer.js @@ -40,18 +40,22 @@ test('loadImages loads a DataURL image and creates a step.', function (t){ t.end(); }); -test('loadImages loads a PATH image and creates a step. (NodeJS)', function (t){ - if(sequencer.options.inBrowser){ - t.equal("not applicable","not applicable","Not applicable for Browser"); - t.end(); - } - else { - sequencer.loadImages(red); +if(!sequencer.options.inBrowser) + test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){ + 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(); + }); + }); + +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(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists"); t.end(); - } -}); + }); test('loadImage works too.', function (t){ sequencer.loadImage('test2',red);