mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-11 19:00:00 +01:00
[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:
committed by
Jeffrey Warren
parent
61b2d75383
commit
ea2069d7f6
14
README.md
14
README.md
@@ -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
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
18
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -31,6 +31,7 @@ module.exports = function Contrast(options, UI) {
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback,
|
||||
inBrowser: options.inBrowser,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
4
src/modules/Invert/index.js
Normal file
4
src/modules/Invert/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
];
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
47
src/modules/_nomodule/node-gifshot.js
Normal file
47
src/modules/_nomodule/node-gifshot.js
Normal 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;
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
30
test/core/gifs/gif-test.js
Normal file
30
test/core/gifs/gif-test.js
Normal file
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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user