[GCI] Experimental GIF Manipulation (#1404)

* wasmSuccess

* modules use wasmSuccess

* modules use wasmSuccess

* add the tooltip

* add GIF support

* fix imageDImensions function

* fix inputCoordinatesParser function

* show correct image dimensions

* show correct image dimensions

* don't allow save as PDF for GIFs

* fix QR module

* fix BlobAnalysis

* fix Blur

* fix Colorbar

* fix Crop

* fix crop defaults

* fix DrawRectangle

* fix EdgeDetect

* fix FlipImage

* fix Gradient

* fix Invert

* fix Overlay

* fix Resize

* fix Rotate

* fix TextOverlay

* fix parse input test

* make GIFs work in nodejs

* sample GIF test

* small change

* cleanup

* cleanup

* cleanup

* small fix

* small change

* handle errors

* proper error handling and fix tests

* cleanup

* try a fix

* try another fix

* fix module benchmarks

* try more fixes

* revert

* try fixing the tests

* fix overlay test

* add the gif tests

* remove unnecessary changes

* fix tests

* whoops

* add some docs

* inBrowser

* fix all module tests

Co-authored-by: Jeffrey Warren <jeff@unterbahn.com>
This commit is contained in:
Harsh Khandeparkar
2020-01-11 22:35:10 +05:30
committed by Jeffrey Warren
parent 61b2d75383
commit ea2069d7f6
50 changed files with 922 additions and 613 deletions

View File

@@ -599,3 +599,17 @@ let sequencer = ImageSequencer() // also for wasm mode i.e. default mode
let sequencer = ImageSequencer({useWasm:false}) //for non-wasm mode
```
## Experimental GIF processing support
ImageSequencer currently can process GIFs but only for most of the modules. Every frame of the GIF is manipulated sequentially (parallel processing would be preferable in the future).
The final frames are then converted back to a GIF but in the process, the time duration of each frame is lost and defaults to `0.1s`.
Modules that do not work:
1. ColorBar (Will get fixed upon fixing overlay as this is a meta module which uses overlay)
2. FisheyeGL
4. Overlay
5. Text Overlay (Almost fixed)
6. Blend
7. Histogram
8. WebGL Distort

View File

@@ -270,7 +270,8 @@ window.onload = function () {
* @param {string} imageDataURL - The data URL for the image.
*/
function savePDF(imageDataURL) {
sequencer.getImageDimensions(imageDataURL, function(dimensions) {
sequencer.getImageDimensions(imageDataURL, function(dimensions, isGIF) {
if (!isGIF) {
// Get the dimensions of the image.
let pageWidth = dimensions.width;
let pageHeight = dimensions.height;
@@ -287,6 +288,8 @@ window.onload = function () {
// Save the pdf with the filename specified here:
pdf.save('index.pdf');
}
else console.log('GIFs cannot be converted to PDF');
});
}

View File

@@ -362,8 +362,8 @@ function DefaultHtmlStepUi(_sequencer, options) {
*
*/
function updateDimensions(step){
_sequencer.getImageDimensions(step.imgElement.src, function (dim) {
step.ui.querySelector('.' + step.name).attributes['data-original-title'].value = `<div style="text-align: center"><p>Image Width: ${dim.width}<br>Image Height: ${dim.height}</br></div>`;
_sequencer.getImageDimensions(step.imgElement.src, function (dim, isGIF) {
step.ui.querySelector('.' + step.name).attributes['data-original-title'].value = `<div style="text-align: center"><p>Image Width: ${dim.width}<br>Image Height: ${dim.height}</br>${isGIF ? `Frames: ${dim.frames}` : ''}</div>`;
});
}

18
package-lock.json generated
View File

@@ -10364,7 +10364,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"requires": {
"graceful-fs": "^4.1.2",
@@ -12466,6 +12466,14 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
@@ -15849,14 +15857,6 @@
"signal-exit": "^3.0.2"
}
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"xhr-write-stream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/xhr-write-stream/-/xhr-write-stream-0.1.2.tgz",

View File

@@ -5,7 +5,7 @@
"main": "src/ImageSequencer.js",
"scripts": {
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/*.js | tap-spec; node test/core/sequencer/benchmark.js; grunt tests; cat ./output/core-tests.js | tape-run --render=\"tap-spec\"",
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/*.js | tap-spec; node test/core/sequencer/benchmark.js | tap-spec; node test/core/gifs/gif-test.js | tap-spec; grunt tests; cat ./output/core-tests.js | tape-run --render=\"tap-spec\"",
"test-ui": "node node_modules/jasmine/bin/jasmine test/ui/spec/*.js",
"test-ui-2": "node ./node_modules/.bin/jest",
"setup": "npm i && npm i -g grunt grunt-cli && grunt build $$ npm rebuild --build-from-source",

View File

@@ -30,7 +30,8 @@ module.exports = {
'grid-overlay': require('./modules/GridOverlay'),
'import-image': require('./modules/ImportImage'),
'minify-image': require('./modules/MinifyImage'),
'invert': require('image-sequencer-invert'),
// 'invert': require('image-sequencer-invert'),
'invert': require('./modules/Invert'),
'ndvi': require('./modules/Ndvi'),
'ndvi-colormap': require('./modules/NdviColormap'),
'noise-reduction': require('./modules/NoiseReduction'),

View File

@@ -1,3 +1,4 @@
const _ = require('lodash');
module.exports = function AddQR(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
@@ -13,17 +14,18 @@ module.exports = function AddQR(options, UI) {
var step = this;
return getPixels(input.src, function(err, oldPixels) {
function changePixel(r, g, b, a) {
return [r, g, b, a];
}
function extraManipulation(pixels, generateOutput) {
if (err) {
console.log(err);
return;
}
require('./QR')(options, pixels, oldPixels, generateOutput);
function extraManipulation(pixels, setRenderState, generateOutput) {
const oldPixels = _.cloneDeep(pixels);
setRenderState(false); // Prevent rendering of final output image until extraManipulation completes.
require('./QR')(options, pixels, oldPixels, () => {
setRenderState(true); // Allow rendering in the callback.
generateOutput();
});
}
function output(image, datauri, mimetype, wasmSuccess) {
@@ -41,7 +43,6 @@ module.exports = function AddQR(options, UI) {
callback: callback,
useWasm:options.useWasm
});
});
}
return {

View File

@@ -1,44 +1,37 @@
module.exports = exports = function (options, pixels, oldPixels, callback) {
const pixelSetter = require('../../util/pixelSetter.js');
const pixelSetter = require('../../util/pixelSetter.js'),
getPixels = require('get-pixels'),
QRCode = require('qrcode');
module.exports = exports = function (options, pixels, oldPixels, cb) {
var QRCode = require('qrcode');
QRCode.toDataURL(options.qrCodeString, function (err, url) {
var getPixels = require('get-pixels');
QRCode.toDataURL(options.qrCodeString, {width: options.size, scale: 1}, function (error, url) {
getPixels(url, function (err, qrPixels) {
if (err) {
console.log('Bad image path', image);
console.log('get-pixels error: ', err);
}
var imagejs = require('imagejs');
var bitmap = new imagejs.Bitmap({ width: qrPixels.shape[0], height: qrPixels.shape[1] });
bitmap._data.data = qrPixels.data;
var resized = bitmap.resize({
width: options.size, height: options.size,
algorithm: 'bicubicInterpolation'
});
qrPixels.data = resized._data.data;
qrPixels.shape = [options.size, options.size, 4];
qrPixels.stride[1] = 4 * options.size;
var width = oldPixels.shape[0],
const width = oldPixels.shape[0],
height = oldPixels.shape[1];
var xe = width - options.size,
const xe = width - options.size, // Starting pixel coordinates
ye = height - options.size;
for (var m = 0; m < width; m++) {
for (var n = 0; n < height; n++) {
if (m >= xe && n >= ye) {
pixelSetter(m, n, [qrPixels.get(m - xe, n - ye, 0), qrPixels.get(m - xe, n - ye, 1), qrPixels.get(m - xe, n - ye, 2), qrPixels.get(m - xe, n - ye, 3)], pixels);
for (let x = xe; x < width; x++) {
for (let y = ye; y < height; y++) {
pixelSetter(
x,
y,
[
qrPixels.get(x - xe, y - ye, 0),
qrPixels.get(x - xe, y - ye, 1),
qrPixels.get(x - xe, y - ye, 2),
qrPixels.get(x - xe, y - ye, 3)
],
pixels
);
}
}
else {
pixelSetter(m, n, [oldPixels.get(m, n, 0), oldPixels.get(m, n, 1), oldPixels.get(m, n, 2), oldPixels.get(m, n, 3)], pixels);
}
}
}
callback();
if(cb) cb();
});
});
};

View File

@@ -54,6 +54,7 @@ module.exports = function Average(options, UI) {
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
ui: options.step.ui,
inBrowser: options.inBrowser,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,

View File

@@ -1,17 +1,10 @@
module.exports = function(pixels, options, priorStep){
var $ = require('jquery'); // To make Blob-analysis work in Node
var img = $(priorStep.imgElement);
if(Object.keys(img).length === 0){
img = $(priorStep.options.step.imgElement);
}
module.exports = function(pixels){
var canvas = document.createElement('canvas');
canvas.width = pixels.shape[0];
canvas.height = pixels.shape[1];
var ctx = canvas.getContext('2d');
ctx.drawImage(img[0], 0, 0);
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.shape[0], pixels.shape[1]), 0, 0);
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);

View File

@@ -10,11 +10,9 @@ module.exports = function BlobAnalysis(options, UI){
var step = this;
var priorStep = this.getStep(-1); // Get the previous step to process it
function extraManipulation(pixels){
pixels = require('./BlobAnalysis')(pixels, options, priorStep);
pixels = require('./BlobAnalysis')(pixels);
return pixels;
}

View File

@@ -1,3 +1,31 @@
// Generates a 5x5 Gaussian kernel
function kernelGenerator(sigma = 1) {
let kernel = [],
sum = 0;
if (sigma == 0) sigma += 0.05;
const s = 2 * Math.pow(sigma, 2);
for (let y = -2; y <= 2; y++) {
kernel.push([]);
for (let x = -2; x <= 2; x++) {
let r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
kernel[y + 2].push(Math.exp(-(r / s)));
sum += kernel[y + 2][x + 2];
}
}
for (let x = 0; x < 5; x++){
for (let y = 0; y < 5; y++){
kernel[y][x] = (kernel[y][x] / sum);
}
}
return kernel;
}
module.exports = exports = function(pixels, blur) {
const pixelSetter = require('../../util/pixelSetter.js');
@@ -34,32 +62,4 @@ module.exports = exports = function(pixels, blur) {
}
return pixels;
// Generates a 5x5 Gaussian kernel.
function kernelGenerator(sigma = 1) {
let kernel = [],
sum = 0;
if (sigma == 0) sigma += 0.05;
const s = 2 * Math.pow(sigma, 2);
for (let y = -2; y <= 2; y++) {
kernel.push([]);
for (let x = -2; x <= 2; x++) {
let r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
kernel[y + 2].push(Math.exp(-(r / s)));
sum += kernel[y + 2][x + 2];
}
}
for (let x = 0; x < 5; x++){
for (let y = 0; y < 5; y++){
kernel[y][x] = (kernel[y][x] / sum);
}
}
return kernel;
}
};

View File

@@ -27,6 +27,7 @@ module.exports = function Blur(options, UI) {
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
ui: options.step.ui,
inBrowser: options.inBrowser,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,

View File

@@ -5,8 +5,8 @@ module.exports = require('../../util/createMetaModule.js')(
return [
{ 'name': 'gradient', 'options': {} },
{ 'name': 'colormap', 'options': { colormap: options.colormap || defaults.colormap } },
{ 'name': 'crop', 'options': { 'y': 0, 'h': options.h || defaults.h } },
{ 'name': 'overlay', 'options': { 'x': options.x || defaults.x, 'y': options.y || defaults.y, 'offset': -4 } }
{ 'name': 'crop', 'options': { 'y': 0, 'w': '100%', 'h': options.h || defaults.h } },
{ 'name': 'overlay', 'options': { 'x': options.x || defaults.h, 'y': options.y || defaults.y, 'offset': -4 } }
];
}, {
infoJson: require('./info.json')

View File

@@ -31,6 +31,7 @@ module.exports = function Contrast(options, UI) {
format: input.format,
image: options.image,
callback: callback,
inBrowser: options.inBrowser,
useWasm:options.useWasm
});

View File

@@ -29,6 +29,7 @@ module.exports = function Convolution(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -1,61 +1,71 @@
module.exports = function Crop(input, options, callback) {
const ndarray = require('ndarray'),
pixelSetter = require('../../util/pixelSetter'),
parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
module.exports = function Crop(pixels, options, cb) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var getPixels = require('get-pixels'),
savePixels = require('save-pixels');
options.x = options.x || defaults.x;
options.y = options.y || defaults.y;
options.x = parseInt(options.x) || defaults.x;
options.y = parseInt(options.y) || defaults.y;
options.w = options.w || defaults.w;
options.h = options.h || defaults.h;
getPixels(input.src, function(err, pixels){
options.w = parseInt(options.w) || Math.floor(pixels.shape[0]);
options.h = parseInt(options.h) || Math.floor(pixels.shape[1]);
options.backgroundColor = options.backgroundColor || defaults.backgroundColor;
var ox = options.x;
var oy = options.y;
var w = options.w;
var h = options.h;
var iw = pixels.shape[0]; //Width of Original Image
var ih = pixels.shape[1]; //Height of Original Image
var backgroundArray = [];
backgroundColor = options.backgroundColor.substring(options.backgroundColor.indexOf('(') + 1, options.backgroundColor.length - 1); // extract only the values from rgba(_,_,_,_)
backgroundColor = backgroundColor.split(',');
for(var i = 0; i < w ; i++){
backgroundArray = backgroundArray.concat([backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]]);
}
// var newarray = new Uint8Array(4*w*h);
var array = [];
for (var n = oy; n < oy + h; n++) {
var offsetValue = 4 * w * n;
if(n < ih){
var start = n * 4 * iw + ox * 4;
var end = n * 4 * iw + ox * 4 + 4 * w;
var pushArray = Array.from(pixels.data.slice(start, end ));
array.push.apply(array, pushArray);
} else {
array.push.apply(array, backgroundArray);
const bg = options.backgroundColor.replace('rgba', '').replace('(', '').replace(')', '').split(',');
let iw = pixels.shape[0], // Width of Original Image
ih = pixels.shape[1], // Height of Original Image
offsetX,
offsetY,
w,
h;
// Parse the inputs
parseCornerCoordinateInputs({iw, ih},
{
x: { valInp: options.x, type: 'horizontal' },
y: { valInp: options.y, type: 'vertical' },
w: { valInp: options.w, type: 'horizontal' },
h: { valInp: options.h, type: 'vertical' },
}, function (opt, coord) {
offsetX = Math.floor(coord.x.valInp);
offsetY = Math.floor(coord.y.valInp);
w = Math.floor(coord.w.valInp);
h = Math.floor(coord.h.valInp);
});
const newPixels = new ndarray([], [w, h, 4]);
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
pixelSetter(x, y, bg, newPixels); // Set the background color
}
}
var newarray = Uint8Array.from(array);
pixels.data = newarray;
pixels.shape = [w, h, 4];
pixels.stride[1] = 4 * w;
for (
let x = 0;
x < Math.min(w - 1, offsetX + iw - 1);
x++
) {
for (
let y = 0;
y < Math.min(h - 1, offsetY + ih - 1);
y++
) {
const inputImgX = x + offsetX,
inputImgY = y + offsetY;
options.format = input.format;
pixelSetter(x, y, [
pixels.get(inputImgX, inputImgY, 0),
pixels.get(inputImgX, inputImgY, 1),
pixels.get(inputImgX, inputImgY, 2),
pixels.get(inputImgX, inputImgY, 3)
], newPixels); // Set the background color
}
}
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, options.format);
if (cb) cb();
r.on('data', function(chunk){
totalLength += chunk.length;
chunks.push(chunk);
});
r.on('end', function(){
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + options.format + ';base64,' + data;
callback(datauri, options.format);
});
});
return newPixels;
};

View File

@@ -1,3 +1,4 @@
const pixelManipulation = require('../_nomodule/PixelManipulation');
/*
* Image Cropping module
* Usage:
@@ -26,54 +27,35 @@ module.exports = function CropModule(options, UI) {
var step = this;
// save the input image;
// TODO: this should be moved to module API to persist the input image
options.step.input = input.src;
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
//parse the inputs
parseCornerCoordinateInputs(options, {
src: input.src,
x: { valInp: options.x, type: 'horizontal' },
y: { valInp: options.y, type: 'vertical' },
w: { valInp: options.w, type: 'horizontal' },
h: { valInp: options.h, type: 'vertical' },
}, function (options, coord) {
options.x = parseInt(coord.x.valInp);
options.y = parseInt(coord.y.valInp);
options.w = coord.w.valInp;
options.h = coord.h.valInp;
});
require('./Crop')(input, options, function (out, format) {
// This output is accessible to Image Sequencer
step.output = {
src: out,
format: format
};
// This output is accessible to the UI
options.step.output = out;
// Tell the UI that the step has been drawn
UI.onComplete(options.step);
// we should do this via event/listener:
function extraManipulation(pixels) {
const newPixels = require('./Crop')(pixels, options, function() {
// We should do this via event/listener:
if (ui && ui.hide) ui.hide();
// start custom UI setup (draggable UI)
// only once we have an input image
// Start custom UI setup (draggable UI)
// Only once we have an input image
if (setupComplete === false && options.step.inBrowser && !options.noUI) {
setupComplete = true;
ui.setup();
}
// Tell Image Sequencer that step has been drawn
callback();
});
return newPixels;
}
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
return pixelManipulation(input, {
output: output,
ui: options.step.ui,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});
}
return {

View File

@@ -16,12 +16,12 @@
"w": {
"type": "string",
"desc": "Width of crop",
"default": "(50%)"
"default": "50%"
},
"h": {
"type": "string",
"desc": "Height of crop",
"default": "(50%)"
"default": "50%"
},
"backgroundColor": {
"type": "text",

View File

@@ -37,6 +37,7 @@ module.exports = function DoNothing(options, UI) {
ui: options.step.ui,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -25,6 +25,7 @@ module.exports = function Dither(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -4,22 +4,24 @@ module.exports = exports = function(pixels, options){
options.startingX = options.startingX || defaults.startingX;
options.startingY = options.startingY || defaults.startingY;
var ox = Number(options.startingX),
oy = Number(options.startingY),
iw = pixels.shape[0],
ih = pixels.shape[1],
thickness = Number(options.thickness) || defaults.thickness,
ex = options.endX = Number(options.endX) - thickness || iw - 1,
ey = options.endY = Number(options.endY) - thickness || ih - 1,
ex = Number(options.endX) - thickness || iw - 1,
ey = Number(options.endY) - thickness || ih - 1,
color = options.color || defaults.color;
color = color.substring(color.indexOf('(') + 1, color.length - 1); // extract only the values from rgba(_,_,_,_)
color = color.substring(color.indexOf('(') + 1, color.length - 1); // Extract only the values from rgba(_,_,_,_)
color = color.split(',');
var drawSide = function(startX, startY, endX, endY){
for (var n = startX; n <= endX + thickness; n++){
for (var k = startY; k <= endY + thickness; k++){
pixelSetter(n, k, [color[0], color[1], color[2]], pixels); //to remove 4th channel - pixels.set(n, k, 3, color[3]);
pixelSetter(n, k, [color[0], color[1], color[2]], pixels); // To remove 4th channel - pixels.set(n, k, 3, color[3]);
}
}
};

View File

@@ -30,6 +30,7 @@ module.exports = function DrawRectangle(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -1,4 +1,5 @@
/**
const Blur = require('../Blur/Blur');
/*
* Detect Edges in an Image
* Uses Canny method for the same
* Read more: https://en.wikipedia.org/wiki/Canny_edge_detector
@@ -21,22 +22,14 @@ module.exports = function edgeDetect(options, UI) {
var step = this;
// Blur the image.
const internalSequencer = ImageSequencer({ inBrowser: false, ui: false });
return internalSequencer.loadImage(input.src, function() {
internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]); // Blurs the image before detecting edges to reduce noise.
return internalSequencer.run(function onCallback(internalOutput) {
require('get-pixels')(internalOutput, function(err, blurPixels) {
if (err) {
return;
}
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution.
// Makes the image greyscale
function changePixel(r, g, b, a) {
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
}
function extraManipulation() {
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
function extraManipulation(pixels) {
const blurPixels = Blur(pixels, options.blur);
return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
}
@@ -55,9 +48,6 @@ module.exports = function edgeDetect(options, UI) {
callback: callback,
useWasm: options.useWasm
});
});
});
});
}
return {

View File

@@ -1,3 +1,4 @@
const _ = require('lodash');
/*
* Flip the image on vertical/horizontal axis.
*/
@@ -5,8 +6,7 @@ module.exports = function FlipImage(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.Axis = options.Axis || defaults.Axis;
var output,
getPixels = require('get-pixels');
let output;
function draw(input, callback, progressObj) {
@@ -15,15 +15,12 @@ module.exports = function FlipImage(options, UI) {
var step = this;
return getPixels(input.src, function(err, oldPixels) {
function changePixel(r, g, b, a) {
return [r, g, b, a];
}
function extraManipulation(pixels) {
if (err) {
console.log(err);
return;
}
const oldPixels = _.cloneDeep(pixels);
return require('./flipImage')(oldPixels, pixels, options.Axis);
}
@@ -42,8 +39,6 @@ module.exports = function FlipImage(options, UI) {
callback: callback,
useWasm:options.useWasm
});
});
}
return {

View File

@@ -1,52 +1,41 @@
module.exports = function Invert(options, UI) {
const pixelSetter = require('../../util/pixelSetter.js');
const pixelSetter = require('../../util/pixelSetter.js'),
pixelManipulation = require('../_nomodule/PixelManipulation');
module.exports = function Gradient(options, UI) {
var output;
// The function which is called on every draw.
function draw(input, callback) {
var getPixels = require('get-pixels');
var savePixels = require('save-pixels');
var step = this;
getPixels(input.src, function(err, pixels) {
if (err) {
console.log('Bad Image path');
return;
}
var width = pixels.shape[0];
for (var i = 0; i < pixels.shape[0]; i++) {
for (var j = 0; j < pixels.shape[1]; j++) {
let val = (i / width) * 255;
pixelSetter(i, j, [val, val, val, 255], pixels);
}
}
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, input.format, { quality: 100 });
r.on('data', function(chunk) {
totalLength += chunk.length;
chunks.push(chunk);
});
r.on('end', function() {
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + input.format + ';base64,' + data;
output(input.image, datauri, input.format);
callback();
});
});
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
function extraManipulation(pixels) {
const [w, h] = pixels.shape;
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
let val = (i / w) * 255;
pixelSetter(i, j, [val, val, val, 255], pixels);
}
}
return pixels;
}
return pixelManipulation(input, {
output,
extraManipulation,
callback,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
useWasm:options.useWasm
});
}
return {

View File

@@ -1,3 +1,4 @@
const pixelManipulation = require('../_nomodule/PixelManipulation');
/*
* Invert the image
*/
@@ -21,7 +22,7 @@ function Invert(options, UI) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
return input.pixelManipulation({
return pixelManipulation(input, {
output: output,
changePixel: changePixel,
format: input.format,
@@ -40,10 +41,4 @@ function Invert(options, UI) {
UI: UI
};
}
var info = {
'name': 'invert',
'description': 'Inverts the image.',
'inputs': {
}
};
module.exports = [Invert, info];
module.exports = Invert;

View File

@@ -0,0 +1,4 @@
module.exports = [
require('./Module'),
require('./info.json')
];

View File

@@ -25,6 +25,7 @@ module.exports = function NoiseReduction(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -18,16 +18,6 @@ module.exports = function Dynamic(options, UI, util) {
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
//parse the inputs
parseCornerCoordinateInputs(options, {
src: input.src,
x: { valInp: options.x, type: 'horizontal' },
y: { valInp: options.y, type: 'vertical' },
}, function(options, input) {
options.x = parseInt(input.x.valInp);
options.y = parseInt(input.y.valInp);
});
// save the pixels of the base image
var baseStepImage = this.getStep(options.offset).image;
var baseStepOutput = this.getOutput(options.offset);
@@ -35,6 +25,19 @@ module.exports = function Dynamic(options, UI, util) {
var getPixels = require('get-pixels');
getPixels(input.src, function(err, pixels) {
// parse the inputs
parseCornerCoordinateInputs({
iw: pixels.shape[0],
ih: pixels.shape[1]
},
{
x: { valInp: options.x, type: 'horizontal' },
y: { valInp: options.y, type: 'vertical' },
}, function(opt, input) {
options.x = parseInt(input.x.valInp);
options.y = parseInt(input.y.valInp);
});
options.secondImagePixels = pixels;
function changePixel(r1, g1, b1, a1, x, y) {

View File

@@ -29,6 +29,7 @@ module.exports = function ReplaceColor(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -1,59 +1,71 @@
const imagejs = require('imagejs'),
pixelSetter = require('../../util/pixelSetter'),
ndarray = require('ndarray');
/*
* Resize the image by given percentage value
*/
module.exports = function Resize(options, UI) {
var output;
let output;
function draw(input, callback, progressObj) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
const defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.resize = options.resize || defaults.resize;
progressObj.stop(true);
progressObj.overrideFlag = true;
var step = this;
var imagejs = require('imagejs');
function changePixel(r, g, b, a) {
return [r, g, b, a];
}
const step = this;
function extraManipulation(pixels) {
// value above 100% scales up, and below 100% scales down
var resize_value = parseInt(options.resize.slice(0, -1));
// Value above 100% scales up, and below 100% scales down
const resize_value = parseInt(options.resize.slice(0, -1));
var new_width,
new_height;
if (resize_value == 100) return pixels;
new_width = Math.round(pixels.shape[0] * (resize_value / 100));
const new_width = Math.round(pixels.shape[0] * (resize_value / 100)),
new_height = Math.round(pixels.shape[1] * (resize_value / 100));
var bitmap = new imagejs.Bitmap({ width: pixels.shape[0], height: pixels.shape[1] });
bitmap._data.data = pixels.data;
const bitmap = new imagejs.Bitmap({ width: pixels.shape[0], height: pixels.shape[1] });
for (let x = 0; x < pixels.shape[0]; x++) {
for (let y = 0; y < pixels.shape[1]; y++) {
let r = pixels.get(x, y, 0),
g = pixels.get(x, y, 1),
b = pixels.get(x, y, 2),
a = pixels.get(x, y, 3);
var resized = bitmap.resize({
width: new_width, height: new_height,
bitmap.setPixel(x, y, r, g, b, a);
}
}
const resized = bitmap.resize({
width: new_width,
height: new_height,
algorithm: 'bicubicInterpolation'
});
pixels.data = resized._data.data;
pixels.shape = [new_width, new_height, 4];
pixels.stride[1] = 4 * new_width;
const newPix = new ndarray([], [new_width, new_height, 4]);
return pixels;
for (let x = 0; x < new_width; x++) {
for (let y = 0; y < new_height; y++) {
const {r, g, b, a} = resized.getPixel(x, y);
pixelSetter(x, y, [r, g, b, a], newPix);
}
}
return newPix;
}
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
ui: options.step.ui,
changePixel: changePixel,
extraManipulation: extraManipulation,
format: input.format,
image: options.image,

View File

@@ -3,34 +3,57 @@
*/
module.exports = function Rotate(options, UI) {
var output;
let output;
function draw(input, callback, progressObj) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
const defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.rotate = options.rotate || defaults.rotate;
progressObj.stop(true);
progressObj.overrideFlag = true;
var step = this;
var imagejs = require('imagejs');
const step = this;
function changePixel(r, g, b, a) {
return [r, g, b, a];
}
function extraManipulation(pixels) {
var rotate_value = (options.rotate) % 360;
var radians = (Math.PI) * rotate_value / 180;
var width = pixels.shape[0];
var height = pixels.shape[1];
var cos = Math.cos(radians);
var sin = Math.sin(radians);
//final dimensions after rotation
var pixels2 = require('ndarray')(new Uint8Array(4 * (Math.floor(Math.abs(width * cos) + Math.abs(height * sin) + 5) * (Math.floor(Math.abs(width * sin) + Math.abs(height * cos)) + 5))).fill(0), [Math.floor(Math.abs(width * cos) + Math.abs(height * sin)) + 5, Math.floor(Math.abs(width * sin) + Math.abs(height * cos)) + 4, 4]);
pixels = require('./Rotate')(pixels, pixels2, options, rotate_value, width, height, cos, sin);
const rotate_value = (options.rotate) % 360;
radians = (Math.PI) * rotate_value / 180,
width = pixels.shape[0],
height = pixels.shape[1],
cos = Math.cos(radians),
sin = Math.sin(radians);
// Final dimensions after rotation
const finalPixels = require('ndarray')(
new Uint8Array(
4 *
(
Math.floor(
Math.abs(width * cos) +
Math.abs(height * sin) +
5
) *
(
Math.floor(
Math.abs(width * sin) +
Math.abs(height * cos)
) +
5
)
)
).fill(255),
[
Math.floor(Math.abs(width * cos) + Math.abs(height * sin)) + 5,
Math.floor(Math.abs(width * sin) + Math.abs(height * cos)) + 4,
4
]
);
pixels = require('./Rotate')(pixels, finalPixels, rotate_value, width, height, cos, sin);
return pixels;
}

View File

@@ -1,38 +1,81 @@
module.exports = function Rotate(pixels, pixels2, options, rotate_value, width, height, cos, sin){
var imagejs = require('imagejs');
var height_half = Math.floor(height / 2);
var width_half = Math.floor(width / 2);
var dimension = width + height;
const imagejs = require('imagejs'),
ndarray = require('ndarray'),
pixelSetter = require('../../util/pixelSetter');
if (rotate_value % 360 == 0)
return pixels;
function copyPixel(x1, y1, x2, y2,pixel_set,pixel_get){
pixel_set.set(x1, y1, 0, pixel_get.get(x2, y2, 0));
pixel_set.set(x1, y1, 1, pixel_get.get(x2, y2, 1));
pixel_set.set(x1, y1, 2, pixel_get.get(x2, y2, 2));
pixel_set.set(x1, y1, 3, pixel_get.get(x2, y2, 3));
module.exports = function Rotate(pixels, finalPixels, rotate_value, width, height, cos, sin){
const height_half = Math.floor(height / 2),
width_half = Math.floor(width / 2);
dimension = width + height;
if (rotate_value % 360 == 0) return pixels;
function copyPixel(x1, y1, x2, y2, finalPix, initPix) {
finalPix.set(x1, y1, 0, initPix.get(x2, y2, 0));
finalPix.set(x1, y1, 1, initPix.get(x2, y2, 1));
finalPix.set(x1, y1, 2, initPix.get(x2, y2, 2));
finalPix.set(x1, y1, 3, initPix.get(x2, y2, 3));
}
pixels1 = require('ndarray')(new Uint8Array(4 * dimension * dimension).fill(0), [dimension, dimension, 4]);
//copying all the pixels from image to pixels1
for (var n = 0; n < pixels.shape[0]; n++){
for (var m = 0; m < pixels.shape[1]; m++){
copyPixel(n + height_half, m + width_half, n, m,pixels1,pixels);
}
}
//rotating pixels1
var bitmap = new imagejs.Bitmap({ width: pixels1.shape[0], height: pixels1.shape[1] });
bitmap._data.data = pixels1.data;
const intermediatePixels = new ndarray(
new Uint8Array(4 * dimension * dimension).fill(255),
[dimension, dimension, 4]
); // Intermediate ndarray of pixels with a greater size to prevent clipping.
var rotated = bitmap.rotate({
// Copying all the pixels from image to intermediatePixels
for (let x = 0; x < pixels.shape[0]; x++){
for (let y = 0; y < pixels.shape[1]; y++){
copyPixel(x + height_half, y + width_half, x, y, intermediatePixels, pixels);
}
}
// Rotating intermediatePixels
const bitmap = new imagejs.Bitmap({ width: intermediatePixels.shape[0], height: intermediatePixels.shape[1] });
for (let x = 0; x < intermediatePixels.shape[0]; x++) {
for (let y = 0; y < intermediatePixels.shape[1]; y++) {
let r = intermediatePixels.get(x, y, 0),
g = intermediatePixels.get(x, y, 1),
b = intermediatePixels.get(x, y, 2),
a = intermediatePixels.get(x, y, 3);
bitmap.setPixel(x, y, r, g, b, a);
}
}
const rotated = bitmap.rotate({
degrees: rotate_value,
});
pixels1.data = rotated._data.data;
//cropping extra whitespace
for (var n = 0; n < pixels2.shape[0]; n++){
for (var m = 0; m < pixels2.shape[1]; m++){
copyPixel(n, m, n + Math.floor(dimension / 2 - Math.abs(width * cos / 2) - Math.abs(height * sin / 2)) - 1, m + Math.floor(dimension / 2 - Math.abs(height * cos / 2) - Math.abs(width * sin / 2)) - 1,pixels2,pixels1);
for (let x = 0; x < intermediatePixels.shape[0]; x++) {
for (let y = 0; y < intermediatePixels.shape[1]; y++) {
const {r, g, b, a} = rotated.getPixel(x, y);
pixelSetter(x, y, [r, g, b, a], intermediatePixels);
}
}
return pixels2;
// Cropping extra whitespace
for (let x = 0; x < finalPixels.shape[0]; x++){
for (let y = 0; y < finalPixels.shape[1]; y++){
copyPixel(
x,
y,
x +
Math.floor(
dimension / 2 -
Math.abs(width * cos / 2) -
Math.abs(height * sin / 2)
) - 1,
y +
Math.floor(
dimension / 2 -
Math.abs(height * cos / 2) -
Math.abs(width * sin / 2)
) - 1,
finalPixels,
intermediatePixels
);
}
}
return finalPixels;
};

View File

@@ -7,11 +7,9 @@ module.exports = function TextOverlay(options, UI) {
var step = this;
var priorStep = this.getStep(-1); // get the previous step to add text onto it.
function extraManipulation(pixels) {
//if (options.step.inBrowser)
pixels = require('./TextOverlay')(pixels, options, priorStep);
pixels = require('./TextOverlay')(pixels, options);
return pixels;
}

View File

@@ -1,7 +1,4 @@
module.exports = exports = function(pixels, options, priorstep){
var $ = require('jquery'); // to make text-overlay work for node.js
module.exports = exports = function(pixels, options){
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.text = options.text || defaults.text;
@@ -11,20 +8,18 @@ module.exports = exports = function(pixels, options, priorstep){
options.color = options.color || defaults.color;
options.size = options.size || defaults.size;
var img = $(priorstep.imgElement);
if(Object.keys(img).length === 0){
img = $(priorstep.options.step.imgElement);
}
var canvas = document.createElement('canvas');
canvas.width = pixels.shape[0]; //img.width();
canvas.height = pixels.shape[1]; //img.height();
var ctx = canvas.getContext('2d');
ctx.drawImage(img[0], 0, 0);
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.shape[0], pixels.shape[1]), 0, 0);
ctx.fillStyle = options.color;
ctx.font = options.size + 'px ' + options.font;
ctx.fillText(options.text, options.x, options.y);
var myImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
pixels.data = myImageData.data;
pixels.data = new Uint8Array(myImageData.data);
return pixels;
};

View File

@@ -34,6 +34,7 @@ module.exports = function ImageThreshold(options, UI) {
extraManipulation: extraManipulation,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
callback: callback,
useWasm:options.useWasm
});

View File

@@ -1,3 +1,10 @@
const pixelSetter = require('../../util/pixelSetter.js'),
getPixels = require('get-pixels'),
savePixels = require('save-pixels'),
ndarray = require('ndarray'),
gifshot = require('gifshot'),
fs = require('fs'),
path = require('path');
/*
* General purpose per-pixel manipulation
* accepting a changePixel() method to remix a pixel's channels
@@ -14,8 +21,23 @@ module.exports = function PixelManipulation(image, options) {
// To handle the case where pixelmanipulation is called on the input object itself
// like input.pixelManipulation(options)
const pixelSetter = require('../../util/pixelSetter.js');
let wasmSuccess; // Whether wasm succeded or failed
const isGIF = image.src.includes('image/gif');
let numFrames = 1, // Number of frames: 1 for a still image
frames = [], // Ndarray of pixels of each frame
perFrameShape, // Width, Height and color chanels of each frame
wasmSuccess, // Whether wasm succeded or failed
renderableFrames, // To block rendering in async modules
resolvedFrames = 0; // Number of WASM promises resolved.
/**
* @description Sets the render state of the current frame. True -> Renderable and False -> Not Renderable.
* @param {Boolean} state Render state of the frame
* @returns {Number} Total number of renderable frames.
*/
function setRenderState(state) {
renderableFrames += state ? 1 : -1;
return renderableFrames;
}
if (arguments.length <= 1) {
options = image;
@@ -24,38 +46,18 @@ module.exports = function PixelManipulation(image, options) {
options = options || {};
const getPixels = require('get-pixels'),
savePixels = require('save-pixels');
/**
* @description Returns the DataURI of an image from its pixels
* @param {"ndarray"} pix pixels ndarray of pixels of the image.
* @param {String} format Format/MimeType of the image input.
* @returns {Promise} Promise with DataURI as parameter in the callback.
*/
const getDataUri = (pix, format) => {
return new Promise(resolve => {
let chunks = [],
totalLength = 0;
getPixels(image.src, function (err, pixels) {
if (err) {
console.log('Bad image path', image);
return;
}
if (options.getNeighbourPixel) {
options.getNeighbourPixel.fun = function getNeighborPixel(distX, distY) {
return options.getNeighbourPixel(pixels, x, y, distX, distY);
};
}
// Iterate through pixels
// TODO: this could possibly be more efficient; see
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181
if (options.preProcess) pixels = options.preProcess(pixels); // Allow for preprocessing of pixels.
function extraOperation() {
var res;
if (options.extraManipulation) res = options.extraManipulation(pixels, generateOutput); // extraManipulation is used to manipulate each pixel individually.
// There may be a more efficient means to encode an image object,
// but node modules and their documentation are essentially arcane on this point.
function generateOutput() {
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, options.format, {
let r = savePixels(pix, format, {
quality: 100
});
@@ -65,49 +67,188 @@ module.exports = function PixelManipulation(image, options) {
});
r.on('end', function () {
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + options.format + ';base64,' + data;
let data = Buffer.concat(chunks, totalLength).toString('base64');
let datauri = 'data:image/' + format + ';base64,' + data;
resolve(datauri);
});
});
};
getPixels(image.src, function (err, pixels) {
if (err) {
console.log('get-pixels error: ', err);
return;
}
// There may be a more efficient means to encode an image object,
// but node modules and their documentation are essentially arcane on this point.
function generateOutput() {
if (!(renderableFrames < numFrames) && !(resolvedFrames < numFrames)) {
if (isGIF) {
const dataPromises = []; // Array of all DataURI promises
for (let f = 0; f < numFrames; f++) {
dataPromises.push(getDataUri(frames[f], options.format));
}
Promise.all(dataPromises).then(datauris => {
const gifshotOptions = {
images: datauris,
frameDuration: 1, // Duration of each frame in 1/10 seconds.
numFrames: datauris.length,
gifWidth: perFrameShape[0],
gifHeight: perFrameShape[1]
};
const gifshotCb = out => {
if (out.error) {
console.log('gifshot error: ', out.errorMsg);
}
if (options.output)
options.output(options.image, out.image, 'gif', wasmSuccess);
if (options.callback) options.callback();
};
if (options.inBrowser) {
gifshot.createGIF(gifshotOptions, gifshotCb);
}
else {
const nodejsGIFShot = eval('require')('./node-gifshot');
nodejsGIFShot(gifshotOptions, gifshotCb);
}
});
}
else {
getDataUri(frames[0], options.format).then(datauri => {
if (options.output)
options.output(options.image, datauri, options.format, wasmSuccess);
if (options.callback) options.callback();
});
}
if (res) {
pixels = res;
generateOutput();
} else if (!options.extraManipulation) generateOutput();
}
}
if (!options.changePixel) extraOperation();
// Get pixels of each frame
if (isGIF) {
const { shape } = pixels;
const [
noOfFrames,
width,
height,
channels
] = shape;
numFrames = noOfFrames;
renderableFrames = noOfFrames; // Total number of renderable frames (mutable)
perFrameShape = [width, height, channels]; // Shape of ndarray of each frame
const numPixelsInFrame = width * height;
/* Coalesce the GIF frames (Some GIFs store delta information in between frames
i.e. Only the pixels which change between frames are stored. All these frames need to be
"Coalesced" to get final GIF frame.
More Info: https://www.npmjs.com/package/gif-extract-frames#why
*/
// Credit for the below code: https://www.npmjs.com/package/gif-extract-frames
// We couldn't use the library because it uses ES6 features which cannot be browserified
for (let i = 0; i < numFrames; ++i) {
if (i > 0) {
const currIndex = pixels.index(i, 0, 0, 0);
const prevIndex = pixels.index(i - 1, 0, 0, 0);
for (let j = 0; j < numPixelsInFrame; ++j) {
const curr = currIndex + j * channels;
if (pixels.data[curr + channels - 1] === 0) {
const prev = prevIndex + j * channels;
for (let k = 0; k < channels; ++k) {
pixels.data[curr + k] = pixels.data[prev + k];
}
}
}
}
}
for (let f = 0; f < numFrames; f++) {
frames.push(
new ndarray(
new Uint8Array(
perFrameShape[0] *
perFrameShape[1] *
perFrameShape[2]
),
perFrameShape
)
);
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
for (let c = 0; c < channels; c++) {
frames[f].set(x, y, c, pixels.get(f, x, y, c));
}
}
}
}
}
else {
frames.push(pixels);
}
// Manipulate every frame separately
for (let f = 0; f < numFrames; f++) {
let framePix = frames[f];
if (options.getNeighbourPixel) {
options.getNeighbourPixel.fun = function getNeighborPixel(distX, distY) {
return options.getNeighbourPixel(framePix, x, y, distX, distY);
};
}
// Iterate through framePix
// TODO: this could possibly be more efficient; see
// https://github.com/p-v-o-s/infragram-js/blob/master/public/infragram.js#L173-L181
if (options.preProcess){
frames[f] = options.preProcess(framePix, setRenderState) || framePix; // Allow for preprocessing of framePix.
perFrameShape = frames[f].shape;
}
if (options.changePixel) {
/* Allows for Flexibility
if per pixel manipulation is not required */
const imports = {
env: {
consoleLog: console.log,
perform: function (x, y) {
let pixel = options.changePixel( // changePixel function is run over every pixel.
pixels.get(x, y, 0),
pixels.get(x, y, 1),
pixels.get(x, y, 2),
pixels.get(x, y, 3),
framePix.get(x, y, 0),
framePix.get(x, y, 1),
framePix.get(x, y, 2),
framePix.get(x, y, 3),
x,
y
);
pixelSetter(x, y, pixel, pixels);
pixelSetter(x, y, pixel, framePix);
}
}
};
/**
* @description Pure JS pixelmanipulation fallback when WASM is not working.
*/
function perPixelManipulation() {
for (var x = 0; x < pixels.shape[0]; x++) {
for (var y = 0; y < pixels.shape[1]; y++) {
for (var x = 0; x < framePix.shape[0]; x++) {
for (var y = 0; y < framePix.shape[1]; y++) {
imports.env.perform(x, y);
}
}
@@ -124,46 +265,59 @@ module.exports = function PixelManipulation(image, options) {
).then(bytes =>
WebAssembly.instantiate(bytes, imports)
).then(results => {
results.instance.exports.manipulatePixel(pixels.shape[0], pixels.shape[1], inBrowser, test);
wasmSuccess = true;
results.instance.exports.manipulatePixel(framePix.shape[0], framePix.shape[1], inBrowser, test);
extraOperation();
wasmSuccess = true;
resolvedFrames++;
generateOutput();
}).catch(err => {
console.log(err);
console.log('WebAssembly acceleration errored; falling back to JavaScript in PixelManipulation');
wasmSuccess = false;
perPixelManipulation();
extraOperation();
wasmSuccess = false;
resolvedFrames++;
generateOutput();
});
} else {
}
else {
try{
const fs = require('fs');
const path = require('path');
const wasmPath = path.join(__dirname, '../../../', 'dist', 'manipulation.wasm');
const buf = fs.readFileSync(wasmPath);
WebAssembly.instantiate(buf, imports).then(results => {
results.instance.exports.manipulatePixel(pixels.shape[0], pixels.shape[1], inBrowser, test);
wasmSuccess = true;
results.instance.exports.manipulatePixel(framePix.shape[0], framePix.shape[1], inBrowser, test);
extraOperation();
wasmSuccess = true;
resolvedFrames++;
generateOutput();
});
}
catch(err){
console.log(err);
console.log('WebAssembly acceleration errored; falling back to JavaScript in PixelManipulation');
wasmSuccess = false;
perPixelManipulation();
extraOperation();
}
}
} else {
wasmSuccess = false;
perPixelManipulation();
extraOperation();
wasmSuccess = false;
resolvedFrames++;
generateOutput();
}
}
}
else {
perPixelManipulation();
wasmSuccess = false;
resolvedFrames++;
generateOutput();
}
}
else resolvedFrames++;
if (options.extraManipulation){
frames[f] = options.extraManipulation(framePix, setRenderState, generateOutput) || framePix; // extraManipulation is used to manipulate each pixel individually.
perFrameShape = frames[f].shape;
}
generateOutput();
}
});
};

View File

@@ -0,0 +1,47 @@
/**
* @param {Object} options GIFShot options object
* @param {Function} cb GIFShot callback
* @returns {null}
*/
function nodejsGIFShot(options, cb) {
const puppeteer = eval('require')('puppeteer');
puppeteer.launch(
{
headless: true,
args:['--no-sandbox', '--disable-setuid-sandbox']
}
)
.then(browser => {
browser.newPage().then(page => {
page.goto('about:blank').then(() => {
page.addScriptTag({ path: require('path').join(__dirname, '../../../node_modules/gifshot/dist/gifshot.min.js') })
.then(() => {
page.evaluate(options => {
return new Promise(resolve => {
gifshot.createGIF(options, resolve);
});
},
options
)
.then(obj => {
browser.close().then(() => {
if (cb) cb(obj);
});
})
.catch(e => {
console.log('Puppeteer error: ', e);
browser.close().then(() => {
if (cb) cb({
error: true,
errorMsg: e
});
});
});
});
});
});
});
}
module.exports = nodejsGIFShot;

View File

@@ -1,24 +1,23 @@
module.exports = function parseCornerCoordinateInputs(options, coord, callback) {
var getPixels = require('get-pixels');
getPixels(coord.src, function(err, pixels) {
var iw = pixels.shape[0],
ih = pixels.shape[1];
if (!coord.x.valInp) {
return;
}
else {
Object.keys(coord).forEach(convert);
const {iw, ih} = options;
function convert(key) {
var val = coord[key];
val.valInp = val.valInp.toString();
val.valInp = val.valInp.replace(/[\)\(]/g, '');
if (val.valInp && val.valInp.slice(-1) === '%') {
val.valInp = parseInt(val.valInp, 10);
if (val.type === 'horizontal')
val.valInp = val.valInp * iw / 100;
else
val.valInp = val.valInp * ih / 100;
}
val.valInp = val.valInp.replace('%', '');
val.valInp = parseInt(val.valInp);
if (val.type === 'horizontal') val.valInp = val.valInp * iw / 100;
else val.valInp = val.valInp * ih / 100;
}
else val.valInp = parseInt(val.valInp);
}
Object.keys(coord).forEach(convert);
callback(options, coord);
});
};

View File

@@ -1,10 +1,29 @@
const getPixels = require('get-pixels');
module.exports = function getImageDimensions(img, cb) {
var getPixels = require('get-pixels');
var dimensions = { width: '', height: '' };
let dimensions;
let isGIF;
getPixels(img, function(err, pixels) {
dimensions.width = pixels.shape[0];
dimensions.height = pixels.shape[1];
cb(dimensions);
if (pixels.shape.length === 4) {
const [frames, width, height] = pixels.shape;
dimensions = {
frames,
width,
height
};
isGIF = true;
}
else {
const [width, height] = pixels.shape;
dimensions = {
width,
height
};
isGIF = false;
}
if (cb) cb(dimensions, isGIF);
});
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -42,19 +42,22 @@ function runBenchmarks(sequencer, t) {
function cb() {
var end = Date.now();
console.log('Module ' + mods[0] + ' ran in: ' + (end - global.start) + ' milliseconds');
mods.splice(0, 1);
if (mods.length > 1) { // Last one is test module, we need not benchmark it
sequencer.steps[global.idx].output.src = image;
global.idx++;
if (mods[0] === 'import-image' || (!!sequencer.modulesInfo(mods[0]).requires && sequencer.modulesInfo(mods[0]).requires.includes('webgl'))) {
mods.splice(0, 1);
while (mods[0] === 'import-image' || mods[0] === 'minify-image' || (!!sequencer.modulesInfo(mods[0]).requires && sequencer.modulesInfo(mods[0]).requires.includes('webgl'))) {
/* Not currently working for this module, which is for importing a new image */
console.log('Bypassing import-image');
console.log(`Bypassing ${mods[0]}`);
mods.splice(0, 1);
}
sequencer.addSteps(mods[0]);
global.start = Date.now();
sequencer.run({ index: global.idx }, cb);
} else {
}
else {
t.end();
}
}

View File

@@ -38,11 +38,11 @@ module.exports = (moduleName, options, benchmark, input) => {
sequencer.run({mode: 'test'}, () => {
let result = sequencer.steps[1].output.src;
base64Img.imgSync(result, target, 'result');
base64Img.imgSync(benchmark, target, 'benchmark');
base64Img.imgSync(result, target, `${moduleName}-result`);
base64Img.imgSync(benchmark, target, `${moduleName}-benchmark`);
result = './test_outputs/result.png';
benchmark = './test_outputs/benchmark.png';
result = `./test_outputs/${moduleName}-result.png`;
benchmark = `./test_outputs/${moduleName}-benchmark.png`;
looksSame(result, benchmark, function(err, res) {
if (err) console.log(err);

View File

@@ -1,17 +1,18 @@
var test = require('tape');
var red = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z';
var parseCornerCoordinateInputs = require('../../../src/util/ParseInputCoordinates');
test('parseCornerCoordinateInputs works.', function (t) {
var options = { x: '10%' },
coord = { src: red, x: { valInp: options.x, type: 'horizontal' } };
var options = { x: '10%', iw: 10, ih: 10 },
coord = { x: { valInp: options.x, type: 'horizontal' } };
callback = function (options, coord) {
options.x = parseInt(coord.x.valInp);
t.equal(options.x, 1, 'parseCornerCoordinateInputs works.');
t.equal(options.x, 1, 'parseCornerCootesrdinateInputs Works.');
t.equal(typeof options.x, 'number', 'Correct output type');
t.end();
};
parseCornerCoordinateInputs(options, coord, callback);
});