Compare commits

..

2 Commits

Author SHA1 Message Date
Harsh Khandeparkar
d8f2f1e8b4 Merge branch 'main' into sashadev-sky-patch-1 2019-12-06 21:20:53 +05:30
Sasha Boginsky
b7e97f315c Update CODE_OF_CONDUCT.md
update to reflect the updates on 
https://publiclab.org/conduct Aug 28 2019, and also standardize format to match LDI, Mk, etc.
2019-09-05 14:48:51 -04:00
203 changed files with 2588 additions and 11143 deletions

View File

@@ -2,7 +2,7 @@ Fixes #0000 (<=== Replace `0000` with the Issue Number)
Make sure these boxes are checked before your pull request (PR) is ready to be reviewed and merged. Thanks! Make sure these boxes are checked before your pull request (PR) is ready to be reviewed and merged. Thanks!
* [ ] tests pass -- look for a green checkbox ✔️ a few minutes after opening your PR -- or run tests locally with `npm run test-all` * [ ] tests pass -- look for a green checkbox ✔️ a few minutes after opening your PR -- or run tests locally with `npm test`
* [ ] code is in uniquely-named feature branch and has no merge conflicts * [ ] code is in uniquely-named feature branch and has no merge conflicts
* [ ] PR is descriptively titled * [ ] PR is descriptively titled
* [ ] ask `@publiclab/is-reviewers` for help, in a comment below * [ ] ask `@publiclab/is-reviewers` for help, in a comment below

View File

@@ -1,16 +0,0 @@
tasks:
- init: npm run setup
command: npm start
ports:
- port: 3000
onOpen: open-preview
github:
prebuilds:
branches: true
pullRequests: true
pullRequestsFromForks: true
addCheck: true
addComment: true
addBadge: false
addLabel: false

View File

@@ -10,22 +10,10 @@ before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter - chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build - ./cc-test-reporter before-build
jobs: script:
include: - npm test
- name: "Base istanbul/tape node tests" - npm run test-ui
script: npm test - grunt build
- name: "Benchmark tests"
script: npm run benchmark
- name: "Gif tests"
script: npm run gif-test
- name: "Browserify core tests and run"
script: grunt tests && npm run core-tests
- name: "Jasmine UI tests (mocked browser env)"
script: npm run test-ui
- name: "jest-puppeteer UI tests (full browser env)"
script: npm run test-ui-2
- name: "Grunt build test of dev environment"
script: grunt build
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - bash <(curl -s https://codecov.io/bash)
after_script: after_script:

View File

@@ -2,7 +2,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify-es'); grunt.loadNpmTasks('grunt-contrib-uglify-es');
grunt.loadNpmTasks('grunt-browser-sync'); grunt.loadNpmTasks('grunt-browser-sync');
grunt.loadNpmTasks('grunt-text-replace');
require('matchdep') require('matchdep')
.filterDev('grunt-*') .filterDev('grunt-*')
@@ -22,11 +21,6 @@ module.exports = function(grunt) {
}, },
browserify: { browserify: {
options: {
alias: {
'gpu.js': './node_modules/gpu.js/src/index.js'
}
},
core: { core: {
src: ['src/ImageSequencer.js'], src: ['src/ImageSequencer.js'],
dest: 'dist/image-sequencer.js' dest: 'dist/image-sequencer.js'
@@ -42,28 +36,6 @@ module.exports = function(grunt) {
produi: { produi: {
src: ['examples/demo.js'], src: ['examples/demo.js'],
dest: 'dist/image-sequencer-ui.brow.js' dest: 'dist/image-sequencer-ui.brow.js'
},
tests: {
src: ['test/core/sequencer/meta-modules.js',
'test/core/sequencer/image-sequencer.js',
'test/core/sequencer/chain.js',
'test/core/sequencer/replace.js',
'test/core/sequencer/import-export.js',
'test/core/sequencer/run.js',
'test/core/sequencer/dynamic-imports.js',
'test/core/util/*.js'],
dest: './output/core-tests.js'
}
},
replace: {
version: {
src: ['examples/sw.js'],
overwrite: true,
replacements: [{
from: /image-sequencer-static-v.*/g,
to: "image-sequencer-static-v<%= pkg.version %>';"
}]
} }
}, },
@@ -97,10 +69,8 @@ module.exports = function(grunt) {
/* Default (development): Watch files and build on change. */ /* Default (development): Watch files and build on change. */
grunt.registerTask('default', ['watch']); grunt.registerTask('default', ['watch']);
grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'replace:version', 'uglify:core', 'uglify:ui']); grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'uglify:core', 'uglify:ui']);
grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'replace:version', 'browserSync', 'watch']); grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'browserSync', 'watch']);
grunt.registerTask('compile', ['browserify:core', 'browserify:ui']); grunt.registerTask('compile', ['browserify:core', 'browserify:ui']);
grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'replace:version', 'uglify:prodcore', 'uglify:produi']); grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'uglify:prodcore', 'uglify:produi']);
grunt.registerTask('tests', ['browserify:tests']);
}; };

View File

@@ -1,9 +1,8 @@
Image Sequencer Image Sequencer
==== ====
[![Code of Conduct](https://img.shields.io/badge/code-of%20conduct-green.svg)](https://publiclab.org/conduct)
[![Build Status](https://travis-ci.org/publiclab/image-sequencer.svg?branch=master)](https://travis-ci.org/publiclab/image-sequencer) [![Maintainability](https://api.codeclimate.com/v1/badges/5906996dd2e90aca6398/maintainability)](https://codeclimate.com/github/publiclab/image-sequencer/maintainability) [![Codecov](https://img.shields.io/codecov/c/github/publiclab/image-sequencer.svg?logo=codecov)](https://codecov.io/gh/publiclab/image-sequencer) [![Build Status](https://travis-ci.org/publiclab/image-sequencer.svg?branch=master)](https://travis-ci.org/publiclab/image-sequencer) [![Maintainability](https://api.codeclimate.com/v1/badges/5906996dd2e90aca6398/maintainability)](https://codeclimate.com/github/publiclab/image-sequencer/maintainability) [![Codecov](https://img.shields.io/codecov/c/github/publiclab/image-sequencer.svg?logo=codecov)](https://codecov.io/gh/publiclab/image-sequencer)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/from-referrer/)
- **Latest Stable Demo**: https://sequencer.publiclab.org - **Latest Stable Demo**: https://sequencer.publiclab.org
- **Latest Beta Demo**: https://beta.sequencer.publiclab.org - **Latest Beta Demo**: https://beta.sequencer.publiclab.org
@@ -72,12 +71,6 @@ In case of a port conflict please run the following
npm i -g http-server ; http-server -p 3000 npm i -g http-server ; http-server -p 3000
``` ```
### Online one-click setup for contributing
Contribute to ImageSequencer using a fully featured online development environment that will automatically: clone the repo, install the dependencies and start the webserver.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)
### Browser ### Browser
Just include [image-sequencer.min.js](https://github.com/publiclab/image-sequencer/blob/stable/dist/image-sequencer.min.js) in the Head section of your web page. See the [demo here](https://sequencer.publiclab.org)! Just include [image-sequencer.min.js](https://github.com/publiclab/image-sequencer/blob/stable/dist/image-sequencer.min.js) in the Head section of your web page. See the [demo here](https://sequencer.publiclab.org)!
@@ -606,17 +599,3 @@ let sequencer = ImageSequencer() // also for wasm mode i.e. default mode
let sequencer = ImageSequencer({useWasm:false}) //for non-wasm 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

@@ -8,44 +8,40 @@ List of Module Documentations
3. [Blend](#blend-module) 3. [Blend](#blend-module)
4. [Blur](#blur-module) 4. [Blur](#blur-module)
5. [Brightness](#brightness-module) 5. [Brightness](#brightness-module)
6. [Canvas-Resize](#canvas-resize-module) 6. [Channel](#channel-module)
7. [Channel](#channel-module) 7. [Colorbar](#colorbar-module)
8. [Colorbar](#colorbar-module) 8. [Colormap](#colormap-module)
9. [Colormap](#colormap-module) 9. [ColorTemperature](#color-temperature)
10. [ColorTemperature](#color-temperature) 10. [Contrast](#contrast-module)
11. [Contrast](#contrast-module) 11. [Convolution](#convolution-module)
12. [Convolution](#convolution-module) 12. [Crop](#crop-module)
13. [Crop](#crop-module) 13. [DecodeQr](#decodeQr-module)
14. [DecodeQr](#decodeQr-module) 14. [Dither](#dither-module)
15. [Dither](#dither-module) 15. [DrawRectangle](#draw-rectangle-module)
16. [DrawRectangle](#draw-rectangle-module) 16. [Dynamic](#dynamic-module)
17. [Dynamic](#dynamic-module) 17. [Edge-Detect](#edge-detect-module)
18. [Edge-Detect](#edge-detect-module) 18. [FisheyeGl](#fisheyeGl-module)
19. [Exposure](#exposure-module) 19. [FlipImage](#flipimage-module)
20. [FisheyeGl](#fisheyeGl-module) 20. [Gamma-Correction](#gamma-correction-module)
21. [FlipImage](#flipimage-module) 21. [Gradient](#gradient-module)
22. [Gamma-Correction](#gamma-correction-module) 22. [Grid-Overlay](#grid-overlay)
23. [Gradient](#gradient-module) 23. [Histogram](#histogram-module)
24. [Grid-Overlay](#grid-overlay) 24. [Import-image](#import-image-module)
25. [Histogram](#histogram-module) 25. [Invert](#invert-module)
26. [Import-image](#import-image-module) 26. [MinifyImage](#minify-image)
27. [Invert](#invert-module) 27. [Ndvi](#ndvi-module)
28. [MinifyImage](#minify-image) 28. [Ndvi-Colormap](#ndvi-colormap-module)
29. [Ndvi](#ndvi-module) 29. [NoiseReduction](#noise-reduction)
30. [Ndvi-Colormap](#ndvi-colormap-module) 30. [Overlay](#overlay-module)
31. [NoiseReduction](#noise-reduction) 31. [PaintBucket](#paint-bucket-module)
32. [Overlay](#overlay-module) 32. [ReplaceColor](#replacecolor-module)
33. [PaintBucket](#paint-bucket-module) 33. [Resize](#resize-module)
34. [ReplaceColor](#replacecolor-module) 34. [Rotate](#rotate-module)
35. [Resize](#resize-module) 35. [Saturation](#saturation-module)
36. [Rotate](#rotate-module) 36. [Segmented-Colormap](#segmented-colormap-module)
37. [Saturation](#saturation-module) 37. [Text-Overlay](#text-overlay)
38. [Segmented-Colormap](#segmented-colormap-module) 38. [Threshold](#threshold)
39. [Text-Overlay](#text-overlay) 39. [Tint](#tint)
40. [Threshold](#threshold)
41. [Tint](#tint)
42. [WebGL-Distort](#webgl-distort-module)
43. [White-Balance](#white-balance-module)
## add-qr-module ## add-qr-module
@@ -78,8 +74,7 @@ This module is used for averaging all the pixels of the image.
## blend-module ## blend-module
This module is used for blending two images. For More info read: _[wiki](https://en.wikipedia.org/wiki/Blend_modes)_ This module is used for blending two images .
#### Usage #### Usage
```js ```js
@@ -90,12 +85,8 @@ This module is used for blending two images. For More info read: _[wiki](https:
where `options` is an object with the following properties: where `options` is an object with the following properties:
* offset: step of image with which current image is to be blended(Two steps back is -2, three steps back is -3 etc; default -2) * offset: step of image with which current image is to be blended(Two steps back is -2, three steps back is -3 etc; default -2)
* blendMode: Blending mode to use for blending two images by default it uses the given function
* func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }) * func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] })
[More info for different blend modes can be found here](http://docs.gimp.org/en/gimp-concepts-layer-modes.html)
## Blob Analysis ## Blob Analysis
This module uses Opencv.js for detecting and marking blob/region in microscopic images. It requires an opencv.js file to This module uses Opencv.js for detecting and marking blob/region in microscopic images. It requires an opencv.js file to
@@ -140,24 +131,6 @@ where `options` is an object with the following property:
* brightness : brightness of the image in percentage (0 to 100; default 100) * brightness : brightness of the image in percentage (0 to 100; default 100)
## canvas-resize-module
This module is used for resizing the canvas of the image.
#### Usage
```js
sequencer.loadImage('PATH')
.addSteps('canvas-resize',options)
.run()
```
where `options` is an object with the following property:
* width: final width of the canvas (default 1000)
* height: final height of the canvas (default 1000)
* x: x-coordinate of the top left of the image on the canvas (default 500)
* y: y-coordinate of the top left of the image on the canvas (default 500)
## channel-module ## channel-module
This module is used for forming a grayscale image by applying one of the three primary colors. This module is used for forming a grayscale image by applying one of the three primary colors.
@@ -354,21 +327,6 @@ where `options` is an object with the following properties:
* lowThresholdratio : Lower Threshold Ratio ( default : 0.2) * lowThresholdratio : Lower Threshold Ratio ( default : 0.2)
## exposure-module
This module is used for changing the exposure of the image.
#### Usage
```js
sequencer.loadImage('PATH')
.addSteps('exposure',options)
.run()
```
where `options` is an object with the following property:
* exposure: exposure value for the new image (-3 to 4; default 1)
## fisheyeGl-module ## fisheyeGl-module
This module is used for correcting Fisheye or Lens Distortion This module is used for correcting Fisheye or Lens Distortion
@@ -709,38 +667,3 @@ It adds color tint to an image
where `options` is an object with the following property: where `options` is an object with the following property:
* color : RGB values seperated by a space (default "0 0 255") * color : RGB values seperated by a space (default "0 0 255")
* factor : amount of tint (default 0.5) * factor : amount of tint (default 0.5)
## webgl-distort-module
This module is used for transforming the perspective of images based on corner coordinates.
#### Usage
```js
sequencer.loadImage('PATH')
.addSteps('webgl-distort',options)
.run()
```
where `options` is an object with the following property:
* nw: top-left corner x and y coordinates separated by a comma (default "0,100")
* ne: top-right corner x and y coordinates separated by a comma (default "1023,-50")
* se: bottom-right corner x and y coordinates separated by a comma (default "1223,867")
* sw: bottom-left corner x and y coordinates separated by a comma (default "100,767")
## white-balance-module
This module is used for rendering neutral colors of an image correctly based on the whitest pixel in the image.
#### Usage
```js
sequencer.loadImage('PATH')
.addSteps('white-balance',options)
.run()
```
where `options` is an object with the following property:
* red: red component of the whitest pixel (default 255)
* green: green component of the whitest pixel (default 255)
* blue: blue component of the whitest pixel (default 255)

View File

@@ -33,7 +33,7 @@ body > .container-fluid {
.center-align { .center-align {
display: flex; display: flex;
justify-content: left; justify-content: center;
text-align:center; text-align:center;
} }
@@ -69,24 +69,14 @@ body > .container-fluid {
min-width:300px; min-width:300px;
} }
.dropzone input {
max-width: 100%;
}
.import-image-zone {
margin: 10px auto 30px auto;
max-width: 250px;
min-width: 230px;
}
.import-image-zone input {
max-width: 100%;
}
.hover { .hover {
background: #eee; background: #eee;
} }
.dropzone input {
max-width: 100%;
}
.step { .step {
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -296,74 +286,21 @@ a.name-header{
cursor: default; cursor: default;
} }
.general-tooltip:hover{ .dimension-tooltip:hover{
text-decoration: none; text-decoration: none;
} }
.general-tooltip:focus{ .dimension-tooltip:focus{
outline: none; outline: none;
} }
.general-tooltip:focus-within{ .dimension-tooltip:focus-within{
outline: none; outline: none;
} }
.general-tooltip{ .dimension-tooltip{
position: relative; position: relative;
bottom: 7px; bottom: 7px;
font-size: 16px; font-size: 16px;
color: #444; color: #444;
} }
#version-number-text {
text-align: center;
padding-top: 100px;
color: gray;
}
#version-number-top-right {
position: fixed;
right: 2%;
top: 5%;
color: lightgray;
}
/* Non float rightward alignment*/
.right {
margin-left: auto;
display: block;
}
#update-prompt-modal {
visibility: hidden;
min-width: 250px;
margin-left: -125px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 2px;
padding: 16px;
position: fixed;
z-index: 1000;
left: 10%;
top: 30px;
}
#update-prompt-modal.show {
visibility: visible;
-webkit-animation: fadein 0.5s;
animation: fadein 0.5s;
}
@-webkit-keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@@ -3,48 +3,12 @@ var defaultHtmlSequencerUi = require('./lib/defaultHtmlSequencerUi.js'),
intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'), intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'),
DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'), DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'),
urlHash = require('./lib/urlHash.js'), urlHash = require('./lib/urlHash.js'),
insertPreview = require('./lib/insertPreview.js'), insertPreview = require('./lib/insertPreview.js');
versionManagement = require('./lib/versionManagement.js'),
isGIF = require('../src/util/isGif');
window.onload = function () { window.onload = function () {
sequencer = ImageSequencer(); // Set the global sequencer variable sequencer = ImageSequencer();
options = { function refreshOptions() {
sortField: 'text',
openOnFocus: false,
onInitialize: function () {
this.$control.on('click', () => {
this.ignoreFocusOpen = true;
setTimeout(() => {
// Trigger onFocus and open dropdown.
this.ignoreFocusOpen = false;
}, 50);
});
},
// Open dropdown after timeout of onClick.
onFocus: function () {
if (!this.ignoreFocusOpen) {
this.open();
}
}
};
versionManagement.getLatestVersionNumber(function(versionNumber) {
console.log('The latest NPM version number for Image Sequencer (from GitHub) is v' + versionNumber);
});
console.log('The local version number for Image Sequencer is v' + versionManagement.getLocalVersionNumber());
function displayVersionNumber() {
$('#version-number-text').text('Image Sequencer v' + versionManagement.getLocalVersionNumber());
$('#version-number-top-right').text('v' + versionManagement.getLocalVersionNumber());
}
displayVersionNumber();
function refreshOptions(options) {
// Default options if parameter is empty.
if (options == undefined) options = { sortField: 'text' };
// Load information of all modules (Name, Inputs, Outputs) // Load information of all modules (Name, Inputs, Outputs)
var modulesInfo = sequencer.modulesInfo(); var modulesInfo = sequencer.modulesInfo();
@@ -60,16 +24,15 @@ window.onload = function () {
} }
// Null option // Null option
addStepSelect.append('<option value="" disabled selected>Select a Module</option>'); addStepSelect.append('<option value="" disabled selected>Select a Module</option>');
addStepSelect.selectize(options); addStepSelect.selectize({
sortField: 'text'
});
} }
refreshOptions(options); refreshOptions();
$(window).on('scroll', scrollFunction); $(window).on('scroll', scrollFunction);
/** function scrollFunction() {
* @description Method to toggle the scroll-up arrow.
*/
function scrollFunction(A, B) {
var shouldDisplay = $('body').scrollTop() > 20 || $(':root').scrollTop() > 20; var shouldDisplay = $('body').scrollTop() > 20 || $(':root').scrollTop() > 20;
$('#move-up').css({ $('#move-up').css({
@@ -77,9 +40,7 @@ window.onload = function () {
}); });
} }
/**
* @description Method to scroll to the top of the page.
*/
function topFunction() { function topFunction() {
$('body').animate({scrollTop: 0}); $('body').animate({scrollTop: 0});
$(':root').animate({scrollTop: 0}); $(':root').animate({scrollTop: 0});
@@ -94,7 +55,7 @@ window.onload = function () {
// UI for the overall demo: // UI for the overall demo:
var ui = defaultHtmlSequencerUi(sequencer); var ui = defaultHtmlSequencerUi(sequencer);
// Load image data from URL `src` parameter. // find any `src` parameters in URL hash and attempt to source image from them and run the sequencer
if (urlHash.getUrlHashParameter('src')) { if (urlHash.getUrlHashParameter('src')) {
sequencer.loadImage(urlHash.getUrlHashParameter('src'), ui.onLoad); sequencer.loadImage(urlHash.getUrlHashParameter('src'), ui.onLoad);
} else { } else {
@@ -104,33 +65,25 @@ window.onload = function () {
var resetSequence = function () { var resetSequence = function () {
var r = confirm('Do you want to reset the sequence?'); var r = confirm('Do you want to reset the sequence?');
if (r) if (r)
{ window.location = '/';
window.location.hash = '';
location.reload();
}
}; };
$('#addStep select').on('change', ui.selectNewStepUi); $('#addStep select').on('change', ui.selectNewStepUi);
$('#addStep #add-step-btn').on('click', ui.addStepUi); $('#addStep #add-step-btn').on('click', ui.addStepUi);
$('#resetButton').on('click', resetSequence); $('#resetButton').on('click', resetSequence);
// Module Selector quick buttons click handler. //Module button radio selection
$('.radio-group .radio').on('click', function () { $('.radio-group .radio').on('click', function () {
$(this).parent().find('.radio').removeClass('selected'); $(this).parent().find('.radio').removeClass('selected');
$(this).addClass('selected'); $(this).addClass('selected');
newStep = $(this).attr('data-value'); newStep = $(this).attr('data-value');
//$("#addStep option[value=" + newStep + "]").attr('selected', 'selected');
$('#addStep select').val(newStep); $('#addStep select').val(newStep);
ui.selectNewStepUi(newStep); ui.selectNewStepUi(newStep);
ui.addStepUi(newStep); ui.addStepUi(newStep);
$(this).removeClass('selected'); $(this).removeClass('selected');
}); });
/**
* @method displayMessageOnSaveSequence
* @description When a sequence is saved to a browser, notification is displayed.
* @returns {Null}
*/
function displayMessageOnSaveSequence() { function displayMessageOnSaveSequence() {
$('.savesequencemsg').fadeIn(); $('.savesequencemsg').fadeIn();
setTimeout(function () { setTimeout(function () {
@@ -150,7 +103,7 @@ window.onload = function () {
} }
} }
$('#saveButton').on('click', function () { $('#saveButton').on('click', function () {
// Different handlers triggered for different dropdown options. // different handlers triggered for different dropdown options
let dropDownValue = $('#selectSaveOption option:selected').val(); let dropDownValue = $('#selectSaveOption option:selected').val();
@@ -162,18 +115,14 @@ window.onload = function () {
} }
else if (dropDownValue == 'save-seq') { else if (dropDownValue == 'save-seq') {
saveSequence(); saveSequence();
} else if(dropDownValue == 'save-pdf') {
savePDF(getLastImage());
}
else if (dropDownValue == 'save-to-publiclab.org' ){
SaveToPubliclab();
} }
}); });
let isWorkingOnGifGeneration = false; let isWorkingOnGifGeneration = false;
$('.js-view-as-gif').on('click', function (event) { // GIF generation and display $('.js-view-as-gif').on('click', function (event) {
if (isWorkingOnGifGeneration) return; // Prevent multiple button clicks /* Prevent user from triggering generation multiple times*/
if (isWorkingOnGifGeneration) return;
isWorkingOnGifGeneration = true; isWorkingOnGifGeneration = true;
@@ -182,12 +131,12 @@ window.onload = function () {
button.innerHTML = '<i class="fa fa-circle-o-notch fa-spin"></i>'; button.innerHTML = '<i class="fa fa-circle-o-notch fa-spin"></i>';
try { try {
// Get GIF resources from previous steps /* Get gif resources of previous steps */
let options = getGifResources(); let options = getGifResources();
gifshot.createGIF(options, function (obj) { // GIF generation gifshot.createGIF(options, function (obj) { // gif generation
if (!obj.error) { if (!obj.error) {
// Final GIF encoded with base64 format // Final gif encoded with base64 format
var image = obj.image; var image = obj.image;
var animatedImage = document.createElement('img'); var animatedImage = document.createElement('img');
@@ -197,7 +146,9 @@ window.onload = function () {
let modal = $('#js-download-gif-modal'); let modal = $('#js-download-gif-modal');
$('#js-download-as-gif-button').one('click', function () { $('#js-download-as-gif-button').one('click', function () {
downloadGif(image); // Trigger GIF download // Trigger download
downloadGif(image);
// Close modal
modal.modal('hide'); modal.modal('hide');
}); });
@@ -209,6 +160,7 @@ window.onload = function () {
// Insert image // Insert image
gifContainer.appendChild(animatedImage); gifContainer.appendChild(animatedImage);
// Open modal // Open modal
modal.modal(); modal.modal();
@@ -227,16 +179,16 @@ window.onload = function () {
}); });
function getGifResources() { function getGifResources() {
// Returns an object with specific gif options /* Returns an object with specific gif options */
let imgs = document.getElementsByClassName('step-thumbnail'); let imgs = document.getElementsByClassName('step-thumbnail');
var imgSrcs = []; var imgSrcs = [];
// Pushes image sources of all the modules in the DOM // Pushes image sources of all the modules in dom
for (var i = 0; i < imgs.length; i++) { for (var i = 0; i < imgs.length; i++) {
imgSrcs.push(imgs[i].src); imgSrcs.push(imgs[i].src);
} }
var options = { // GIF frame options var options = { // gif frame options
'gifWidth': imgs[0].width, 'gifWidth': imgs[0].width,
'gifHeight': imgs[0].height, 'gifHeight': imgs[0].height,
'images': imgSrcs, 'images': imgSrcs,
@@ -255,64 +207,11 @@ window.onload = function () {
}); });
} }
/**
* Get the data URL for the last image in the sequence.
* @return {string} The data URL for the last image in the sequence.
*/
function getLastImage() {
// Get the image from the last step.
let imgs = document.getElementsByClassName('step-thumbnail');
let lastStepImage = imgs[imgs.length - 1];
return lastStepImage.getAttribute('src');
}
/**
* Download the given image URL as a PDF file.
* @param {string} imageDataURL - The data URL for the image.
*/
function savePDF(imageDataURL) {
sequencer.getImageDimensions(imageDataURL, function(dimensions) {
if (isGIF(imageDataURL)) {
// Get the dimensions of the image.
let pageWidth = dimensions.width;
let pageHeight = dimensions.height;
// Create a new pdf with the same dimensions as the image.
const pdf = new jsPDF({
orientation: pageHeight > pageWidth ? 'portrait' : 'landscape',
unit: 'px',
format: [pageHeight, pageWidth]
});
// Add the image to the pdf with dimensions equal to the internal dimensions of the page.
pdf.addImage(imageDataURL, 0, 0, pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight());
// Save the pdf with the filename specified here:
pdf.save('index.pdf');
}
else console.log('GIFs cannot be converted to PDF');
});
}
function downloadGif(image) { function downloadGif(image) {
download(image, 'index.gif', 'image/gif'); // Downloadjs library function download(image, 'index.gif', 'image/gif');// downloadjs library function
} }
function SaveToPubliclab() { // image selection and drag/drop handling from examples/lib/imageSelection.js
function postToPL(imgSrc) {
var uniq = Date.now();
$('body').append('<form method="post" id="postToPL' + uniq + '" action="https://publiclab.org/post" target="postToPLWindow"><input type="hidden" name="datauri_main_image" /></form>');
f = $('#postToPL' + uniq)[0];
f.datauri_main_image.value = imgSrc;
window.open('', 'postToPLWindow');
f.submit();
}
postToPL($('img')[sequencer.steps.length - 1].src);
}
// Image selection and drag/drop handling from examples/lib/imageSelection.js
sequencer.setInputStep({ sequencer.setInputStep({
dropZoneSelector: '#dropzone', dropZoneSelector: '#dropzone',
fileInputSelector: '#fileInput', fileInputSelector: '#fileInput',
@@ -327,9 +226,8 @@ window.onload = function () {
step.options.step.imgElement.src = reader.result; step.options.step.imgElement.src = reader.result;
else else
step.imgElement.src = reader.result; step.imgElement.src = reader.result;
insertPreview.updatePreviews(reader.result, '#addStep');
insertPreview.updatePreviews(reader.result, document.querySelector('#addStep')); insertPreview.updatePreviews(sequencer.steps[0].imgElement.src, '.insertDiv');
DefaultHtmlStepUi(sequencer).updateDimensions(step);
}, },
onTakePhoto: function (url) { onTakePhoto: function (url) {
var step = sequencer.steps[0]; var step = sequencer.steps[0];
@@ -339,15 +237,16 @@ window.onload = function () {
step.options.step.imgElement.src = url; step.options.step.imgElement.src = url;
else else
step.imgElement.src = url; step.imgElement.src = url;
insertPreview.updatePreviews(url, document.querySelector('#addStep')); insertPreview.updatePreviews(url, '#addStep');
insertPreview.updatePreviews(sequencer.steps[0].imgElement.src, '.insertDiv');
} }
}); });
setupCache(); setupCache();
if (urlHash.getUrlHashParameter('src')) { // Gets the sequence from the URL if (urlHash.getUrlHashParameter('src')) {
insertPreview.updatePreviews(urlHash.getUrlHashParameter('src'), document.querySelector('#addStep')); insertPreview.updatePreviews(urlHash.getUrlHashParameter('src'), '#addStep');
} else { } else {
insertPreview.updatePreviews('images/tulips.png', document.querySelector('#addStep')); insertPreview.updatePreviews('images/tulips.png', '#addStep');
} }
}; };

View File

@@ -33,12 +33,9 @@
<script src="../node_modules/gifshot/dist/gifshot.min.js" type="text/javascript"></script> <script src="../node_modules/gifshot/dist/gifshot.min.js" type="text/javascript"></script>
<!-- Download.js for large files --> <!-- Download.js for large files -->
<script src="../node_modules/downloadjs/download.min.js" type="text/javascript" ></script> <script src="../node_modules/downloadjs/download.min.js" type="text/javascript" />
<!-- jspdf to enable save image as pdf --> <script src="lib/scrollToTop.js"></script>
<script src="../node_modules/jspdf/dist/jspdf.min.js" type="text/javascript" ></script>
<!-- <script src="lib/scrollToTop.js"></script> -->
<script src="../node_modules/selectize/dist/js/standalone/selectize.min.js"></script> <script src="../node_modules/selectize/dist/js/standalone/selectize.min.js"></script>
</head> </head>
@@ -56,8 +53,6 @@
<link href="./selectize.default.css" rel="stylesheet"> <link href="./selectize.default.css" rel="stylesheet">
<link rel="stylesheet" href="demo.css"> <link rel="stylesheet" href="demo.css">
<div id="update-prompt-modal">A new version of image sequencer is available. Click <a href="#" id="reload">here</a> to update.</div>
<div class="container-fluid"> <div class="container-fluid">
<header class="text-center" style="min-width: 450px"> <header class="text-center" style="min-width: 450px">
@@ -66,7 +61,7 @@
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original
image, it image, it
creates a new image at each step in a sequence. creates a new image at each step in a sequence.
<a href="https://github.com/publiclab/image-sequencer/blob/main/README.md">Learn more</a> <a href="https://publiclab.org/image-sequencer">Learn more</a>
</p> </p>
<p> <p>
Open Source Open Source
@@ -75,11 +70,10 @@
</a> </a>
by <a href="https://publiclab.org" title="Publiclab Website"><i class="fa fa-globe"></i> Publiclab</a> by <a href="https://publiclab.org" title="Publiclab Website"><i class="fa fa-globe"></i> Publiclab</a>
</p> </p>
<span id="version-number-top-right"></span>
</header> </header>
<div id="dropzone" class="dropzone"> <div id="dropzone" class="dropzone">
<p id="dropzone-text"> <p>
<i>Select or drag in an image to start!</i> <i>Select or drag in an image to start!</i>
</p> </p>
<center> <center>
@@ -154,7 +148,7 @@
</select> </select>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<button class="btn btn-primary btn-lg" name="add" id="add-step-btn">Add Step</button></div> <button class="btn btn-success btn-lg" name="add" id="add-step-btn">Add Step</button></div>
</div> </div>
<div class="row center-align"> <div class="row center-align">
<button id="resetButton" class="btn btn-default btn-lg" <button id="resetButton" class="btn btn-default btn-lg"
@@ -196,10 +190,8 @@
<select class="form-control input-md mouse" id="selectSaveOption" style="margin-top:20px"> <select class="form-control input-md mouse" id="selectSaveOption" style="margin-top:20px">
<option value="save-image">Save as PNG</option> <option value="save-image">Save as PNG</option>
<option value="save-gif">Save as GIF (all steps)</option> <option value="save-gif">Save as GIF (all steps)</option>
<option value="save-pdf">Save as PDF</option>
<option value="save-seq">Save sequence</option> <option value="save-seq">Save sequence</option>
<option value="save-seq-string">Save sequence string</option> <option value="save-seq-string">Save sequence string</option>
<option value="save-to-publiclab.org">Save to PublicLab.org</option>>
</select> </select>
<p><button id="saveButton" class="btn btn-primary btn-lg save-button">Save</button></p> <p><button id="saveButton" class="btn btn-primary btn-lg save-button">Save</button></p>
<p><button class="btn btn-default btn-lg js-view-as-gif" id="gif">Preview GIF</button></p> <p><button class="btn btn-default btn-lg js-view-as-gif" id="gif">Preview GIF</button></p>
@@ -231,9 +223,6 @@
</p> </p>
</div> </div>
</div> </div>
<div class="row">
<p id="version-number-text">Unable to load version number</p>
</div>
</footer> </footer>
<button id="move-up"><i class="fa fa-arrow-circle-o-up"></i></button> <button id="move-up"><i class="fa fa-arrow-circle-o-up"></i></button>

View File

@@ -1,41 +1,7 @@
var setupCache = function() { var setupCache = function() {
let newWorker; // When sw.js is changed, this is the new service worker generated.
// Toggle a CSS class to display a popup prompting the user to fetch a new version.
function showUpdateModal() {
$('#update-prompt-modal').addClass('show');
}
/**
* When a new service worker has been loaded, the button in the update prompt
* modal should trigger the skipWaiting event to replace the current
* service worker with the new one.
*/
$('#reload').on('click', function() {
newWorker.postMessage({ action: 'skipWaiting' });
});
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
// Register the service worker.
navigator.serviceWorker.register('sw.js', { scope: '/examples/' }) navigator.serviceWorker.register('sw.js', { scope: '/examples/' })
.then(function(registration) { .then(function(registration) {
registration.addEventListener('updatefound', () => {
// When sw.js has been changed, get a reference to the new service worker.
newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
// Check if service worker state has changed.
switch(newWorker.state) {
case 'installed':
if(navigator.serviceWorker.controller) {
// New service worker available; prompt the user to update.
showUpdateModal();
}
// No updates available; do nothing.
break;
}
});
});
const installingWorker = registration.installing; const installingWorker = registration.installing;
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
console.log(installingWorker); console.log(installingWorker);
@@ -48,17 +14,6 @@ var setupCache = function() {
.catch(function(error) { .catch(function(error) {
console.log('Service worker registration failed, error:', error); console.log('Service worker registration failed, error:', error);
}); });
/**
* This is the event listener for when the service worker updates.
* When the service worker updates, reload the page.
*/
let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', function() {
if(refreshing) return;
window.location.reload();
refreshing = true;
});
} }
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
@@ -79,11 +34,6 @@ var setupCache = function() {
} }
location.reload(); location.reload();
}); });
}; };
module.exports = setupCache; module.exports = setupCache;

View File

@@ -1,5 +1,4 @@
var urlHash = require('./urlHash.js'); var urlHash = require('./urlHash.js');
insertPreview = require('./insertPreview.js');
function DefaultHtmlSequencerUi(_sequencer, options) { function DefaultHtmlSequencerUi(_sequencer, options) {
options = options || {}; options = options || {};
@@ -34,11 +33,6 @@ function DefaultHtmlSequencerUi(_sequencer, options) {
function removeStepUi() { function removeStepUi() {
var index = $(removeStepSel).index(this) + 1; var index = $(removeStepSel).index(this) + 1;
// If last step is removed.
if(sequencer.steps.length==index+1){
console.log("inside")
insertPreview.updatePreviews(sequencer.steps[index-1].output.src, document.querySelector('#addStep'));
}
sequencer.removeSteps(index).run({ index: index - 1 }); sequencer.removeSteps(index).run({ index: index - 1 });
// remove from URL hash too // remove from URL hash too
urlHash.setUrlHashParameter('steps', sequencer.toString()); urlHash.setUrlHashParameter('steps', sequencer.toString());

View File

@@ -11,33 +11,27 @@
const intermediateHtmlStepUi = require('./intermediateHtmlStepUi.js'), const intermediateHtmlStepUi = require('./intermediateHtmlStepUi.js'),
urlHash = require('./urlHash.js'), urlHash = require('./urlHash.js'),
_ = require('lodash'), _ = require('lodash'),
insertPreview = require('./insertPreview.js');
mapHtmlTypes = require('./mapHtmltypes'), mapHtmlTypes = require('./mapHtmltypes'),
scopeQuery = require('./scopeQuery'), scopeQuery = require('./scopeQuery');
isGIF = require('../../src/util/isGif');
function DefaultHtmlStepUi(_sequencer, options) { function DefaultHtmlStepUi(_sequencer, options) {
let $step, $stepAll;
options = options || {}; options = options || {};
var stepsEl = options.stepsEl || document.querySelector('#steps'); var stepsEl = options.stepsEl || document.querySelector('#steps');
var selectStepSel = options.selectStepSel = options.selectStepSel || '#selectStep'; var selectStepSel = options.selectStepSel = options.selectStepSel || '#selectStep';
function onSetup(step, stepOptions) { function onSetup(step, stepOptions) {
if (step.options && step.options.description) if (step.options && step.options.description)
step.description = step.options.description; step.description = step.options.description;
step.ui = // Basic UI markup for the step step.ui =
'\ '\
<div class="container-fluid step-container">\ <div class="container-fluid step-container">\
<div class="panel panel-default">\ <div class="panel panel-default">\
<div class="panel-heading">\ <div class="panel-heading">\
<div class="trash-container pull-right">\ <div class="trash-container pull-right"><button type="button" class="btn btn-link ' + step.name + ' dimension-tooltip" data-toggle="tooltip" data-html="true" title="" data-original-title=""><i class="fa fa-info-circle"></i></button></div>\
<a type="button" target="_blank" href="https://developer.mozilla.org/en-US/docs/WebAssembly" style="display: none;" class="btn btn-link general-tooltip wasm-tooltip" data-toggle="tooltip" data-html="true" data-original-title="<div style=\'text-align: center\'><p>This step is Web Assembly accelerated. Click to Read More</div>">\
<i class="fa fa-bolt"></i>\
</a>\
<button type="button" class="btn btn-link ' + step.name + ' general-tooltip dimension-tooltip" data-toggle="tooltip" data-html="true" data-original-title="">\
<i class="fa fa-info-circle"></i>\
</button>\
</div>\
<h3 class="panel-title">' + <h3 class="panel-title">' +
'<span class="toggle mouse">' + step.name + ' <span class="caret toggleIcon rotated"></span>\ '<span class="toggle mouse">' + step.name + ' <span class="caret toggleIcon rotated"></span>\
<span class="load-spin pull-right" style="display:none;padding:1px 8px;"><i class="fa fa-circle-o-notch fa-spin"></i></span>\ <span class="load-spin pull-right" style="display:none;padding:1px 8px;"><i class="fa fa-circle-o-notch fa-spin"></i></span>\
@@ -48,7 +42,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
<div class="row step">\ <div class="row step">\
<div class="col-md-4 details container-fluid">\ <div class="col-md-4 details container-fluid">\
<div class="cal collapse in"><p>' + <div class="cal collapse in"><p>' +
'<a href="https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#' + step.name + '-module">' + (step.description || '') + '</a>' + '<i>' + (step.description || '') + '</i>' +
'</p></div>\ '</p></div>\
</div>\ </div>\
<div class="col-md-8 cal collapse in step-column">\ <div class="col-md-8 cal collapse in step-column">\
@@ -74,20 +68,21 @@ function DefaultHtmlStepUi(_sequencer, options) {
var util = intermediateHtmlStepUi(_sequencer, step); var util = intermediateHtmlStepUi(_sequencer, step);
var parser = new DOMParser(); var parser = new DOMParser();
step.ui = parser.parseFromString(step.ui, 'text/html'); // Convert the markup string to a DOM node. step.ui = parser.parseFromString(step.ui, 'text/html');
step.ui = step.ui.querySelector('div.container-fluid'); step.ui = step.ui.querySelector('div.container-fluid');
step.$step = scopeQuery.scopeSelector(step.ui); // Shorthand methods for scoped DOM queries. Read the docs [CONTRIBUTING.md](https://github.com/publiclab/image-sequencer/blob/main/CONTRIBUTING.md) for more info. $step = scopeQuery.scopeSelector(step.ui);
step.$stepAll = scopeQuery.scopeSelectorAll(step.ui); $stepAll = scopeQuery.scopeSelectorAll(step.ui);
let {$step, $stepAll} = step; step.ui.$step = $step;
step.ui.$stepAll = $stepAll;
step.linkElements = step.ui.querySelectorAll('a'); // All the anchor tags in the step UI step.linkElements = step.ui.querySelectorAll('a');
step.imgElement = $step('a img.img-thumbnail')[0]; // The output image step.imgElement = $step('a img.img-thumbnail')[0];
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) { if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = _sequencer.modulesInfo(step.name).inputs; var inputs = _sequencer.modulesInfo(step.name).inputs;
var outputs = _sequencer.modulesInfo(step.name).outputs; var outputs = _sequencer.modulesInfo(step.name).outputs;
var merged = Object.assign(inputs, outputs); // Combine outputs with inputs var merged = Object.assign(inputs, outputs); // combine outputs w inputs
for (var paramName in merged) { for (var paramName in merged) {
var isInput = inputs.hasOwnProperty(paramName); var isInput = inputs.hasOwnProperty(paramName);
@@ -107,10 +102,10 @@ function DefaultHtmlStepUi(_sequencer, options) {
else { else {
let paramVal = step.options[paramName] || inputDesc.default; let paramVal = step.options[paramName] || inputDesc.default;
if (inputDesc.id == 'color-picker') { // Separate input field for color-picker if (inputDesc.id == 'color-picker') { // separate input field for color-picker
html += html +=
'<div id="color-picker" class="input-group colorpicker-component">' + '<div id="color-picker" class="input-group colorpicker-component">' +
'<input class="form-control color-picker-target" type="' + '<input class="form-control target" type="' +
inputDesc.type + inputDesc.type +
'" name="' + '" name="' +
paramName + paramName +
@@ -118,7 +113,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
paramVal + '">' + '<span class="input-group-addon"><i></i></span>' + paramVal + '">' + '<span class="input-group-addon"><i></i></span>' +
'</div>'; '</div>';
} }
else { // Non color-picker input types else { // use this if the the field isn't color-picker
html = html =
'<input class="form-control target" type="' + '<input class="form-control target" type="' +
inputDesc.type + inputDesc.type +
@@ -160,10 +155,10 @@ function DefaultHtmlStepUi(_sequencer, options) {
</div>'; </div>';
$step('div.details').append(div); $step('div.details').append(div);
} }
$step('div.panel-footer').append( // Save button $step('div.panel-footer').append(
'<div class="cal collapse in"><button type="submit" class="btn btn-sm btn-default btn-save" disabled = "true" >Apply</button> <small style="padding-top:2px;">Press apply to see changes</small></div>' '<div class="cal collapse in"><button type="submit" class="btn btn-sm btn-default btn-save" disabled = "true" >Apply</button> <small style="padding-top:2px;">Press apply to see changes</small></div>'
); );
$step('div.panel-footer').prepend( // Markup for tools: download and insert step buttons $step('div.panel-footer').prepend(
'<button class="pull-right btn btn-default btn-sm insert-step" >\ '<button class="pull-right btn btn-default btn-sm insert-step" >\
<span class="insert-text"><i class="fa fa-plus"></i> Insert Step</span><span class="no-insert-text" style="display:none">Close</span></button>\ <span class="insert-text"><i class="fa fa-plus"></i> Insert Step</span><span class="no-insert-text" style="display:none">Close</span></button>\
<button class="pull-right btn btn-default btn-sm download-btn" style="margin-right:2px" >\ <button class="pull-right btn btn-default btn-sm download-btn" style="margin-right:2px" >\
@@ -178,10 +173,9 @@ function DefaultHtmlStepUi(_sequencer, options) {
parser.parseFromString(tools, 'text/html').querySelector('div') parser.parseFromString(tools, 'text/html').querySelector('div')
); );
$stepAll('.remove').on('click', function() {notify('Step Removed', 'remove-notification');}); // Notification on removal of a step $stepAll('.remove').on('click', function() {notify('Step Removed', 'remove-notification');});
$step('.insert-step').on('click', function() { util.insertStep(step.ID); }); // Insert a step in between the sequence $stepAll('.insert-step').on('click', function() { util.insertStep(step.ID); });
// Insert the step's UI in the right place // Insert the step's UI in the right place
if (stepOptions.index == _sequencer.steps.length) { if (stepOptions.index == _sequencer.steps.length) {
stepsEl.appendChild(step.ui); stepsEl.appendChild(step.ui);
$('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true); $('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true);
@@ -191,30 +185,16 @@ function DefaultHtmlStepUi(_sequencer, options) {
else { else {
stepsEl.insertBefore(step.ui, $(stepsEl).children()[stepOptions.index]); stepsEl.insertBefore(step.ui, $(stepsEl).children()[stepOptions.index]);
} }
// Enable the load-image insert-step button when there are steps after load-image
// The logical operator is `> 0` because the number of steps is found before adding the step, actual logic is `steps.length + 1 > 1` which is later simplified.
if (_sequencer.steps.length > 0) $('#load-image .insert-step').prop('disabled', false);
else $('#load-image .insert-step').prop('disabled', true);
} }
else { else {
$('#load-image').append(step.ui); // Default UI without extra tools for the first step(load image) $('#load-image').append(step.ui);
$step('div.panel-footer').prepend( `
<button class="right btn btn-default btn-sm insert-step" disabled="true">
<span class="insert-text"><i class="fa fa-plus"></i> Insert Step</span>
<span class="no-insert-text" style="display:none">Close</span>
</button>`
);
$step('.insert-step').on('click', function() { util.insertStep(step.ID); });
} }
$step('.toggle').on('click', () => { // Step container dropdown $step('.toggle').on('click', () => {
$step('.toggleIcon').toggleClass('rotated'); $step('.toggleIcon').toggleClass('rotated');
$stepAll('.cal').collapse('toggle'); $stepAll('.cal').collapse('toggle');
}); });
$(step.imgElement).on('mousemove', _.debounce(() => imageHover(step), 150)); // Shows the pixel coordinates on hover $(step.imgElement).on('mousemove', _.debounce(() => imageHover(step), 150));
$(step.imgElement).on('click', (e) => {e.preventDefault(); }); $(step.imgElement).on('click', (e) => {e.preventDefault(); });
$stepAll('#color-picker').colorpicker(); $stepAll('#color-picker').colorpicker();
@@ -231,23 +211,15 @@ function DefaultHtmlStepUi(_sequencer, options) {
}); });
_sequencer.run({ index: step.index - 1 }); _sequencer.run({ index: step.index - 1 });
// Modify the URL hash // modify the url hash
urlHash.setUrlHashParameter('steps', _sequencer.toString()); urlHash.setUrlHashParameter('steps', _sequencer.toString());
// Disable the save button // disable the save button
$step('.btn-save').prop('disabled', true); $step('.btn-save').prop('disabled', true);
optionsChanged = false; optionsChanged = false;
changedInputs = 0; changedInputs = 0;
} }
} }
/**
* @method handleInputValueChange
* @description Enables the save button on input change
* @param {*} currentValue The current value of the input
* @param {*} initValue The initial/old value of the input
* @param {Boolean} hasChangedBefore Whether the input was changed before
* @returns {Boolean} True if the value has changed
*/
function handleInputValueChange(currentValue, initValue, hasChangedBefore) { function handleInputValueChange(currentValue, initValue, hasChangedBefore) {
var inputChanged = !(isNaN(initValue) || isNaN(currentValue) ? currentValue === initValue : currentValue - initValue === 0); var inputChanged = !(isNaN(initValue) || isNaN(currentValue) ? currentValue === initValue : currentValue - initValue === 0);
changedInputs += hasChangedBefore ? inputChanged ? 0 : -1 : inputChanged ? 1 : 0; changedInputs += hasChangedBefore ? inputChanged ? 0 : -1 : inputChanged ? 1 : 0;
@@ -278,21 +250,6 @@ function DefaultHtmlStepUi(_sequencer, options) {
}); });
}); });
$stepAll('.color-picker-target').each(function(i, input) {
$(input)
.data('initValue', $(input).val())
.data('hasChangedBefore', false)
.on('input change', function() {
$(this)
.data('hasChangedBefore',
handleInputValueChange(
$(this).val(),
$(this).data('initValue'),
$(this).data('hasChangedBefore')
)
);
});
});
$('input[type="range"]').on('input', function() { $('input[type="range"]').on('input', function() {
@@ -301,20 +258,17 @@ function DefaultHtmlStepUi(_sequencer, options) {
} }
function onDraw({$step, $stepAll}) { function onDraw() {
$step('.load').show(); $step('.load').show();
$step('img').hide(); $step('img').hide();
$stepAll('.load-spin').show(); $stepAll('.load-spin').show();
} }
function onComplete(step) { function onComplete(step) {
let {$step, $stepAll} = step;
$step('img').show(); $step('img').show();
$stepAll('.load-spin').hide(); $stepAll('.load-spin').hide();
$step('.load').hide(); $step('.load').hide();
$stepAll('.download-btn').off('click');
step.imgElement.src = (step.name == 'load-image') ? step.output.src : step.output; step.imgElement.src = (step.name == 'load-image') ? step.output.src : step.output;
var imgthumbnail = $step('.img-thumbnail').getDomElem(); var imgthumbnail = $step('.img-thumbnail').getDomElem();
for (let index = 0; index < step.linkElements.length; index++) { for (let index = 0; index < step.linkElements.length; index++) {
@@ -322,23 +276,28 @@ function DefaultHtmlStepUi(_sequencer, options) {
step.linkElements[index].href = step.imgElement.src; step.linkElements[index].href = step.imgElement.src;
} }
// TODO: use a generalized version of this. // TODO: use a generalized version of this
function fileExtension(output) { function fileExtension(output) {
return output.split('/')[1].split(';')[0]; return output.split('/')[1].split(';')[0];
} }
$stepAll('.download-btn').on('click', () => { $stepAll('.download-btn').on('click', () => {
for (let index = 0; index < step.linkElements.length; index++){
var element = document.createElement('a'); var element = document.createElement('a');
element.setAttribute('href', step.output); element.setAttribute('href', step.linkElements[index].href);
element.setAttribute('download', step.name + '.' + fileExtension(step.imgElement.src)); element.setAttribute('download', step.name + '.' + fileExtension(step.imgElement.src));
element.style.display = 'none'; element.style.display = 'none';
document.body.appendChild(element); document.body.appendChild(element);
element.click(); element.click();
document.body.removeChild(element);
}
}); });
// Fill inputs with stored step options // fill inputs with stored step options
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) { if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
var inputs = _sequencer.modulesInfo(step.name).inputs; var inputs = _sequencer.modulesInfo(step.name).inputs;
var outputs = _sequencer.modulesInfo(step.name).outputs; var outputs = _sequencer.modulesInfo(step.name).outputs;
@@ -350,7 +309,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
.data('initValue', step.options[i]); .data('initValue', step.options[i]);
if (inputs[i].type.toLowerCase() === 'select') if (inputs[i].type.toLowerCase() === 'select')
$step('div[name="' + i + '"] select') $step('div[name="' + i + '"] select')
.val(String(step.options[i])) .val(step.options[i])
.data('initValue', step.options[i]); .data('initValue', step.options[i]);
} }
} }
@@ -363,38 +322,12 @@ function DefaultHtmlStepUi(_sequencer, options) {
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
updateDimensions(step);
});
if (step.name === 'load-image') insertPreview.updatePreviews(step.output.src, document.querySelector('#addStep'));
else insertPreview.updatePreviews(step.output, document.querySelector('#addStep'));
// Handle the wasm bolt display
if (step.useWasm) {
if (step.wasmSuccess) $step('.wasm-tooltip').fadeIn();
else $step('.wasm-tooltip').fadeOut();
}
else $step('.wasm-tooltip').fadeOut();
}
/**
* @description Updates Dimension of the image
* @param {Object} step - Current Step
* @returns {void}
*
*/
function updateDimensions(step){
_sequencer.getImageDimensions(step.imgElement.src, function (dim) { _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>${isGIF(step.output) ? `Frames: ${dim.frames}` : ''}</div>`; 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>`;
});
}); });
} }
/**
* @method imageHover
* @description Handler to display image coordinates on hover.
* @param {Object} step Current step variable
* @returns {Null}
*/
function imageHover(step){ function imageHover(step){
var img = $(step.imgElement); var img = $(step.imgElement);
@@ -417,28 +350,13 @@ function DefaultHtmlStepUi(_sequencer, options) {
function onRemove(step) { function onRemove(step) {
step.ui.remove(); step.ui.remove();
$('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true); $('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true);
$('div[class*=imgareaselect-]').remove();
// Enable the load-image insert-step button when there are steps after load-image
// The logical operator is `> 2` because the number of steps is found before removing the step, actual logic is `steps.length - 1 > 1` which is later simplified.
if (_sequencer.steps.length - 1 > 1) $('#load-image .insert-step').prop('disabled', false);
else $('#load-image .insert-step').prop('disabled', true);
$(step.imgElement).imgAreaSelect({
remove: true
});
} }
function getPreview() { function getPreview() {
return step.imgElement; return step.imgElement;
} }
/**
* @method notify
* @description General purpose DOM toast notification
* @param {String} msg Message to be displayed
* @param {String} id A unique identifier for the notification
* @returns {Null}
*/
function notify(msg, id){ function notify(msg, id){
if ($('#' + id).length == 0) { if ($('#' + id).length == 0) {
var notification = document.createElement('span'); var notification = document.createElement('span');
@@ -460,8 +378,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
onRemove: onRemove, onRemove: onRemove,
onDraw: onDraw, onDraw: onDraw,
notify: notify, notify: notify,
imageHover: imageHover, imageHover: imageHover
updateDimensions: updateDimensions
}; };
} }

View File

@@ -1,5 +1,5 @@
// Generate downscaled preview images for quick buttons. function generatePreview(previewStepName, customValues, path, selector) {
function generatePreview(previewStepName, customValues, path, DomNode) {
var previewSequencer = ImageSequencer(); var previewSequencer = ImageSequencer();
function insertPreview(src) { function insertPreview(src) {
var img = document.createElement('img'); var img = document.createElement('img');
@@ -8,10 +8,9 @@ function generatePreview(previewStepName, customValues, path, DomNode) {
img.src = src; img.src = src;
$(img).css('max-width', '200%'); $(img).css('max-width', '200%');
$(img).css('transform', 'translateX(-20%)'); $(img).css('transform', 'translateX(-20%)');
$(DomNode.querySelector('.radio-group')).find('.radio').each(function() { $(selector + ' .radio-group').find('div').each(function() {
if ($(this).attr('data-value') === previewStepName) { if ($(this).find('div').attr('data-value') === previewStepName) {
$(this).find('img').remove(); $(this).find('div').append(img);
$(this).append(img);
} }
}); });
} }
@@ -30,8 +29,8 @@ function generatePreview(previewStepName, customValues, path, DomNode) {
previewSequencer.loadImage(path, loadPreview); previewSequencer.loadImage(path, loadPreview);
} }
function updatePreviews(src, DomNode) { function updatePreviews(src, selector) {
$(DomNode).find('img').remove(); $(selector + ' img').remove();
var previewSequencerSteps = { var previewSequencerSteps = {
'resize': '125%', 'resize': '125%',
@@ -42,20 +41,19 @@ function updatePreviews(src, DomNode) {
'crop': { 'crop': {
'x': 0, 'x': 0,
'y': 0, 'y': 0,
'w': '50%', 'w': '(50%)',
'h': '50%', 'h': '(50%)',
'noUI': true 'noUI': true
} }
}; };
var img = new Image(); var img = new Image();
img.onload = function(){ img.onload = function(){
var height = img.height; var height = img.height;
var width = img.width; var width = img.width;
let percentage = (80 / height) * 100; // Take the min resize value that fits the preview area => (new-width/orig_ht) - '80 as the preview area has 80*80 dimension. let percentage = (80 / height) * 100; //take the min resize value that fits the preview area => (new-width/orig_ht) - '80 as the preview area has 80*80 dimension
percentage = Math.max((80 / width) * 100, percentage); // Make sure that one dimension doesn't resize greater, leading distorting preview-area fitting. percentage = Math.max((80 / width) * 100, percentage); // make sure that one dimension doesn't resize greater, leading distorting preview-area fitting
percentage = Math.ceil(percentage); percentage = Math.ceil(percentage);
var sequencer = ImageSequencer(); var sequencer = ImageSequencer();
@@ -64,7 +62,7 @@ function updatePreviews(src, DomNode) {
this.addSteps('resize', {['resize']: percentage + '%'}); this.addSteps('resize', {['resize']: percentage + '%'});
this.run((src)=>{ this.run((src)=>{
Object.keys(previewSequencerSteps).forEach(function (step, index) { Object.keys(previewSequencerSteps).forEach(function (step, index) {
generatePreview(step, Object.values(previewSequencerSteps)[index], src, DomNode); generatePreview(step, Object.values(previewSequencerSteps)[index], src, selector);
}); });
}); });
}); });

View File

@@ -1,17 +1,8 @@
var urlHash = require('./urlHash.js'), var urlHash = require('./urlHash.js'),
insertPreview = require('./insertPreview.js'); insertPreview = require('./insertPreview.js');
/**
* @method IntermediateHtmlStepUi
* @description Inserts a module selector in between the current sequence
* @param {Object} _sequencer Sequencer instance
* @param {Object} step Current step variable
* @param {Object} options Optional options Object
* @returns {Object} Object containing the insertStep function
*/
function IntermediateHtmlStepUi(_sequencer, step, options) { function IntermediateHtmlStepUi(_sequencer, step, options) {
function stepUI() { function stepUI() {
// Basic markup for the selector
return '<div class="row insertDiv collapse">\ return '<div class="row insertDiv collapse">\
<section class="panel panel-primary .insert-step">\ <section class="panel panel-primary .insert-step">\
<button class="btn btn-default close-insert-box"><i class="fa fa-times" aria-hidden="true"></i> Close</button>\ <button class="btn btn-default close-insert-box"><i class="fa fa-times" aria-hidden="true"></i> Close</button>\
@@ -64,7 +55,7 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
</select>\ </select>\
<div>\ <div>\
<div class="col-md-4">\ <div class="col-md-4">\
<button class="btn btn-primary btn-lg insert-save-btn add-step-btn" name="add">Add Step</button>\ <button class="btn btn-success btn-lg insert-save-btn add-step-btn" name="add">Add Step</button>\
<div>\ <div>\
</div>\ </div>\
</div>\ </div>\
@@ -73,13 +64,6 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
</div>'; </div>';
} }
/**
* @method toggleDiv
* @description Toggles the module selector dropdown.
* @param {Object} $step $step util function
* @param {Fucntion} callback Optional callback function
* @returns {Null}
*/
var toggleDiv = function($step, callback = function(){}){ var toggleDiv = function($step, callback = function(){}){
$step('.insertDiv').collapse('toggle'); $step('.insertDiv').collapse('toggle');
if ($step('.insert-text').css('display') != 'none'){ if ($step('.insert-text').css('display') != 'none'){
@@ -90,19 +74,13 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
} }
}; };
/**
* @method insertStep
* @description Handler to insert selected module in the sequence
* @returns {Null}
*/
insertStep = function (id) { insertStep = function (id) {
const $step = step.$step, const $step = step.ui.$step,
$stepAll = step.$stepAll; $stepAll = step.ui.$stepAll;
var modulesInfo = _sequencer.modulesInfo(); var modulesInfo = _sequencer.modulesInfo();
var parser = new DOMParser(); var parser = new DOMParser();
var addStepUI = stepUI(); var addStepUI = stepUI();
addStepUI = parser.parseFromString(addStepUI, 'text/html').querySelector('div'); addStepUI = parser.parseFromString(addStepUI, 'text/html').querySelector('div');
if ($step('.insertDiv').length > 0){ if ($step('.insertDiv').length > 0){
toggleDiv($step); toggleDiv($step);
} }
@@ -113,12 +91,10 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
addStepUI addStepUI
); );
toggleDiv($step, function(){ toggleDiv($step, function(){
if (step.name === 'load-image') insertPreview.updatePreviews(step.output.src, $step('.insertDiv').getDomElem()); insertPreview.updatePreviews(step.output, '.insertDiv');
else insertPreview.updatePreviews(step.output, $step('.insertDiv').getDomElem());
}); });
} }
$step('.insertDiv .close-insert-box').off('click').on('click', function(){ $step('.insertDiv .close-insert-box').off('click').on('click', function(){
toggleDiv($step); toggleDiv($step);
$step('.insertDiv').removeClass('insertDiv'); $step('.insertDiv').removeClass('insertDiv');
@@ -126,7 +102,6 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
var insertStepSelect = $step('.insert-step-select'); var insertStepSelect = $step('.insert-step-select');
insertStepSelect.html(''); insertStepSelect.html('');
// Add modules to the insertStep dropdown // Add modules to the insertStep dropdown
for (var m in modulesInfo) { for (var m in modulesInfo) {
if (modulesInfo[m] && modulesInfo[m].name) if (modulesInfo[m] && modulesInfo[m].name)
@@ -134,30 +109,20 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
'<option value="' + m + '">' + modulesInfo[m].name + '</option>' '<option value="' + m + '">' + modulesInfo[m].name + '</option>'
); );
} }
insertStepSelect.selectize({ insertStepSelect.selectize({
sortField: 'text' sortField: 'text'
}); });
$('.insertDiv .radio-group .radio').on('click', function () { $('.insertDiv .radio-group .radio').on('click', function () {
var newStepName = $(this).attr('data-value'); // Get the name of the module to be inserted var newStepName = $(this).attr('data-value');
id = $($step('.insertDiv').parents()[3]).prevAll().length; id = $($step('.insertDiv').parents()[3]).prevAll().length;
insert(id, $step, newStepName); // Insert the selected module insert(id, $step, newStepName);
}); });
$step('.insertDiv .add-step-btn').on('click', function () { $step('.insertDiv .add-step-btn').on('click', function () {
var newStepName = insertStepSelect.val(); var newStepName = insertStepSelect.val();
id = $($step('.insertDiv').parents()[3]).prevAll().length; id = $($step('.insertDiv').parents()[3]).prevAll().length;
insert(id, $step, newStepName); }); insert(id, $step, newStepName); });
}; };
/**
* @method insert
* @description Inserts the specified step at the specified index in the sequence
* @param {Number} id Index of the step
* @param {Function} $step $step util function
* @param {String} newStepName Name of the new step
*/
function insert(id, $step, newStepName) { function insert(id, $step, newStepName) {
toggleDiv($step); toggleDiv($step);
$step('.insertDiv').removeClass('insertDiv'); $step('.insertDiv').removeClass('insertDiv');

View File

@@ -1,7 +1,3 @@
/**
* @description Maps module input types to their respective html <input> tag types.
* @param {Object} inputInfo Object containing the type and optionally min/max for range type inputs.
*/
function mapHtmlTypes(inputInfo){ function mapHtmlTypes(inputInfo){
var htmlType; var htmlType;
switch(inputInfo.type.toLowerCase()){ switch(inputInfo.type.toLowerCase()){
@@ -24,7 +20,7 @@ function mapHtmlTypes(inputInfo){
htmlType = 'text'; htmlType = 'text';
break; break;
} }
var response = Object.assign({}, inputInfo); var response = inputInfo;
response.type = htmlType; response.type = htmlType;
return response; return response;
} }

View File

@@ -1,7 +1,6 @@
/** /**
* @method $scope * @method $scope
* @param {"DOMNode"} scope A DOM Node as the scope * @param {"DOMNode"} scope A DOM Node as the scope
* @returns {Function} Constructor for the scopeSelector Object.
*/ */
function $scope(scope) { function $scope(scope) {
return function(queryString){ return function(queryString){
@@ -25,7 +24,6 @@ function $scope(scope) {
/** /**
* @method $scopeAll * @method $scopeAll
* @param {"DOMNode"} scope A DOM Node as the scope * @param {"DOMNode"} scope A DOM Node as the scope
* @returns {Function} Constructor for the scopeSelectorAll Object.
*/ */
function $scopeAll(scope){ function $scopeAll(scope){
return function(queryString){ return function(queryString){
@@ -49,8 +47,7 @@ function $scopeAll(scope){
/** /**
* @method scopeSelector * @method scopeSelector
* @description A scoped jQuery selector * @description A scoped jQuery selector
* @param {"DOMNode"} scope A DOM Node as the scope * @param {"DOMNode"} scope DOM Node as the scope
* @returns {Function}
*/ */
function scopeSelector(scope){ function scopeSelector(scope){
return $scope(scope); return $scope(scope);
@@ -59,8 +56,7 @@ function scopeSelector(scope){
/** /**
* @method scopeSelectorAll * @method scopeSelectorAll
* @description A scoped jQuery multiple selector * @description A scoped jQuery multiple selector
* @param {"DOMNode} scope A DOM Node as the scope * @param {"DOMNode} scope DOM Node as the scope
* @returns {Function}
*/ */
function scopeSelectorAll(scope){ function scopeSelectorAll(scope){
return $scopeAll(scope); return $scopeAll(scope);

View File

@@ -1,43 +0,0 @@
/**
* Functions for getting version information.
* Note: these functions are not used by the service worker to check for updates;
* the service worker updates whenever sw.js has changed.
* sw.js is changed when grunt replace:version is run. This task is run during
* grunt build, serve, and productions tasks.
*/
const package = require('../../package.json');
/**
* Get the current version number from package.json on the homepage.
* @param {function} callback The function that uses the version number.
*/
function getLatestVersionNumber(callback) {
// Get the homepage reference from the local package.json.
var homepage = package.homepage;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
var response = JSON.parse(this.responseText);
var latestVersionNumber = response.version;
// Do something with the version number using a callback function.
if (callback)
callback(latestVersionNumber);
}
}
// Get the package.json file from online using a GET request.
request.open("GET", homepage + "/package.json", true);
request.send();
}
// Get the version number from the local package.json file.
function getLocalVersionNumber() {
return package.version;
}
module.exports = {
getLatestVersionNumber,
getLocalVersionNumber
}

View File

@@ -1,4 +1,5 @@
const staticCacheName = 'image-sequencer-static-v3.5.1'; const staticCacheName = 'image-sequencer-static-v3';
self.addEventListener('install', event => { self.addEventListener('install', event => {
console.log('Attempting to install service worker'); console.log('Attempting to install service worker');
}); });
@@ -32,10 +33,3 @@ self.addEventListener('fetch', function(event) {
}) })
); );
}); });
// When the update modal sends a 'skipWaiting' message, call the skipWaiting method.
self.addEventListener('message', function(event) {
if(event.data.action === 'skipWaiting') {
self.skipWaiting();
}
});

View File

@@ -1,11 +0,0 @@
module.exports = {
launch: {
headless: process.env.HEADLESS !== 'false',
},
server: {
command: 'grunt serve',
port:3000,
launchTimeout: 5000000,
},
};

View File

@@ -1,6 +0,0 @@
module.exports = {
preset: 'jest-puppeteer',
testRegex: './*\\.test\\.js$',
verbose: true,
};

6299
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,14 +5,9 @@
"main": "src/ImageSequencer.js", "main": "src/ImageSequencer.js",
"scripts": { "scripts": {
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert", "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;", "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; browserify test/core/sequencer/meta-modules.js test/core/sequencer/image-sequencer.js test/core/sequencer/chain.js test/core/sequencer/replace.js test/core/sequencer/import-export.js test/core/sequencer/run.js test/core/sequencer/dynamic-imports.js test/core/util/*.js | tape-run --render=\"tap-spec\"",
"benchmark" : "node test/core/sequencer/benchmark.js | tap-spec;",
"gif-test" : "node test/core/gifs/gif-test.js | tap-spec;",
"core-tests" : "cat ./output/core-tests.js | tape-run --render=\"tap-spec\"",
"test-all": "npm run test && npm run benchmark && npm run gif-test && grunt tests && npm run core-tests",
"test-ui": "node node_modules/jasmine/bin/jasmine test/ui/spec/*.js", "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",
"setup": "npm i && npm i -g grunt grunt-cli && npm rebuild --build-from-source && grunt build",
"start": "grunt serve" "start": "grunt serve"
}, },
"lint-staged": { "lint-staged": {
@@ -37,12 +32,12 @@
"dependencies": { "dependencies": {
"atob": "^2.1.2", "atob": "^2.1.2",
"base64-img": "^1.0.4", "base64-img": "^1.0.4",
"bootstrap": "^3.4.1", "bootstrap": "~3.4.0",
"bootstrap-colorpicker": "^2.5.3", "bootstrap-colorpicker": "^2.5.3",
"buffer": "~5.6.0", "buffer": "~5.4.0",
"commander": "^4.0.1", "commander": "^3.0.1",
"compressorjs": "^1.0.5", "compressorjs": "^1.0.5",
"data-uri-to-buffer": "^3.0.0", "data-uri-to-buffer": "^2.0.0",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"eslint": "^6.1.0", "eslint": "^6.1.0",
"fisheyegl": "^0.1.2", "fisheyegl": "^0.1.2",
@@ -51,26 +46,23 @@
"get-pixels": "~3.3.0", "get-pixels": "~3.3.0",
"gifshot": "^0.4.5", "gifshot": "^0.4.5",
"glfx": "0.0.4", "glfx": "0.0.4",
"gpu.js": "^2.3.1", "gpu.js": "^2.0.0-rc.12",
"image-sequencer-invert": "^1.0.0", "image-sequencer-invert": "^1.0.0",
"imagejs": "0.0.9", "imagejs": "0.0.9",
"imagemin": "^7.0.1", "imagemin": "^7.0.0",
"imagemin-jpegtran": "^6.0.0", "imagemin-jpegtran": "^6.0.0",
"imagemin-pngquant": "^8.0.0", "imagemin-pngquant": "^8.0.0",
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2", "imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"jasmine": "^3.4.0", "jasmine": "^3.4.0",
"jpegtran-bin": "^4.0.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"jsdom": "^15.0.0", "jsdom": "^15.0.0",
"jspdf": "^1.5.3",
"jsqr": "^1.1.1", "jsqr": "^1.1.1",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"ndarray": "^1.0.18", "ndarray": "^1.0.18",
"opencv.js": "^1.2.1", "opencv.js": "^1.2.1",
"ora": "^4.0.3", "ora": "^3.0.0",
"pace": "0.0.4", "pace": "0.0.4",
"pngquant-bin": "^5.0.2",
"puppeteer": "^1.14.0", "puppeteer": "^1.14.0",
"qrcode": "^1.3.3", "qrcode": "^1.3.3",
"readline-sync": "^1.4.7", "readline-sync": "^1.4.7",
@@ -85,7 +77,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.4.3", "@babel/plugin-proposal-object-rest-spread": "^7.4.3",
"@babel/plugin-syntax-object-rest-spread": "^7.2.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"browserify": "16.5.0", "browserify": "16.2.3",
"eslint": "^6.1.0", "eslint": "^6.1.0",
"grunt": "^1.0.3", "grunt": "^1.0.3",
"grunt-browser-sync": "^2.2.0", "grunt-browser-sync": "^2.2.0",
@@ -93,21 +85,18 @@
"grunt-contrib-concat": "^1.0.1", "grunt-contrib-concat": "^1.0.1",
"grunt-contrib-uglify-es": "^3.3.0", "grunt-contrib-uglify-es": "^3.3.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-text-replace": "^0.4.0",
"husky": "^3.0.5", "husky": "^3.0.5",
"image-filter-core": "~2.0.2", "image-filter-core": "~2.0.2",
"image-filter-threshold": "~2.0.1", "image-filter-threshold": "~2.0.1",
"jasmine-core": "^3.3.0", "jasmine-core": "^3.3.0",
"jasmine-jquery": "^2.1.1", "jasmine-jquery": "^2.1.1",
"jasmine-spec-reporter": "^4.2.1", "jasmine-spec-reporter": "^4.2.1",
"jest": "^25.1.0", "lint-staged": "^9.1.0",
"jest-puppeteer": "^4.3.0",
"lint-staged": "^10.0.3",
"looks-same": "^7.0.0", "looks-same": "^7.0.0",
"matchdep": "^2.0.0", "matchdep": "^2.0.0",
"tap-spec": "^5.0.0", "tap-spec": "^5.0.0",
"tape": "^4.9.2", "tape": "^4.9.2",
"tape-run": "^7.0.0", "tape-run": "^6.0.0",
"uglify-es": "^3.3.7" "uglify-es": "^3.3.7"
}, },
"husky": { "husky": {

View File

@@ -2,11 +2,6 @@ if (typeof window !== 'undefined') { isBrowser = true; }
else { var isBrowser = false; } else { var isBrowser = false; }
require('./util/getStep.js'); require('./util/getStep.js');
/**
* @method ImageSequencer
* @param {Object|Float32Array} options Optional options
* @returns {Object}
*/
ImageSequencer = function ImageSequencer(options) { ImageSequencer = function ImageSequencer(options) {
var str = require('./Strings.js')(this.steps, modulesInfo, addSteps, copy); var str = require('./Strings.js')(this.steps, modulesInfo, addSteps, copy);
@@ -19,12 +14,6 @@ ImageSequencer = function ImageSequencer(options) {
return Object.prototype.toString.call(object).split(' ')[1].slice(0, -1); return Object.prototype.toString.call(object).split(' ')[1].slice(0, -1);
} }
/**
* @method log
* @description Logs colored messages to the console using ASCII color codes
* @param {String} color ASCII color code
* @param {String} msg Message to be logged to the console
*/
function log(color, msg) { function log(color, msg) {
if (options.ui != 'none') { if (options.ui != 'none') {
if (arguments.length == 1) console.log(arguments[0]); if (arguments.length == 1) console.log(arguments[0]);
@@ -32,12 +21,6 @@ ImageSequencer = function ImageSequencer(options) {
} }
} }
/**
* @method copy
* @description Returns a clone of the input object.
* @param {Object|Float32Array} a The Object/Array to be cloned
* @returns {Object|Float32Array}
*/
function copy(a) { function copy(a) {
if (!typeof (a) == 'object') return a; if (!typeof (a) == 'object') return a;
if (objTypeOf(a) == 'Array') return a.slice(); if (objTypeOf(a) == 'Array') return a.slice();
@@ -70,10 +53,10 @@ ImageSequencer = function ImageSequencer(options) {
for (o in sequencer) { for (o in sequencer) {
modules[o] = sequencer[o]; modules[o] = sequencer[o];
} }
sequences = JSON.parse(window.localStorage.getItem('sequences')); // Get saved sequences from localStorage sequences = JSON.parse(window.localStorage.getItem('sequences'));
if (!sequences) { if (!sequences) {
sequences = {}; sequences = {};
window.localStorage.setItem('sequences', JSON.stringify(sequences)); // Set the localStorage entry as an empty Object by default window.localStorage.setItem('sequences', JSON.stringify(sequences));
} }
} }
@@ -81,16 +64,11 @@ ImageSequencer = function ImageSequencer(options) {
// if (options.imageSelect || options.inBrowser) addStep('image-select'); // if (options.imageSelect || options.inBrowser) addStep('image-select');
// else if (options.imageUrl) loadImage(imageUrl); // else if (options.imageUrl) loadImage(imageUrl);
/**
* @method addSteps
* @description Adds one of more steps to the sequence.
* @return {Object}
*/
function addSteps() { function addSteps() {
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer; var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
var args = []; var args = [];
var json_q = {}; var json_q = {};
for (var arg in arguments) { args.push(copy(arguments[arg])); } // Get all the module names from the arguments for (var arg in arguments) { args.push(copy(arguments[arg])); }
json_q = formatInput.call(this_, args, '+'); json_q = formatInput.call(this_, args, '+');
inputlog.push({ method: 'addSteps', json_q: copy(json_q) }); inputlog.push({ method: 'addSteps', json_q: copy(json_q) });
@@ -99,28 +77,17 @@ ImageSequencer = function ImageSequencer(options) {
return this; return this;
} }
/**
* @method removeStep
* @description Removes the step at the specified index from the sequence.
* @param {Object} ref ImageSequencer instance
* @param {Number} index Index of the step to be removed
* @returns {Null}
*/
function removeStep(ref, index) { function removeStep(ref, index) {
// Remove the step from images[image].steps and redraw remaining images //remove the step from images[image].steps and redraw remaining images
if (index > 0) { if (index > 0) {
//var this_ = (this.name == "ImageSequencer") ? this : this.sequencer; //var this_ = (this.name == "ImageSequencer") ? this : this.sequencer;
thisStep = ref.steps[index]; thisStep = ref.steps[index];
thisStep.UI.onRemove(thisStep.options.step); thisStep.UI.onRemove(thisStep.options.step);
ref.steps.splice(index, 1); ref.steps.splice(index, 1);
} }
//tell the UI a step has been removed
} }
/**
* @method removeSteps
* @description Removes one or more steps from the sequence
* @returns {Object}
*/
function removeSteps() { function removeSteps() {
var indices; var indices;
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer; var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
@@ -136,11 +103,6 @@ ImageSequencer = function ImageSequencer(options) {
return this; return this;
} }
/**
* @method insertSteps
* @description Inserts steps at the specified index
* @returns {Object}
*/
function insertSteps() { function insertSteps() {
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer; var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
var args = []; var args = [];
@@ -156,11 +118,8 @@ ImageSequencer = function ImageSequencer(options) {
return this; return this;
} }
/** // Config is an object which contains the runtime configuration like progress bar
* @method run // information and index from which the sequencer should run
* @param {Object} config Object which contains the runtime configuration like progress bar information and index from which the sequencer should run.
* @returns {Boolean}
*/
function run(config) { function run(config) {
var progressObj, index = 0; var progressObj, index = 0;
config = config || { mode: 'no-arg' }; config = config || { mode: 'no-arg' };
@@ -178,7 +137,7 @@ ImageSequencer = function ImageSequencer(options) {
var callback = function() { }; var callback = function() { };
for (var arg in args) for (var arg in args)
if (objTypeOf(args[arg]) == 'Function') if (objTypeOf(args[arg]) == 'Function')
callback = args.splice(arg, 1)[0]; // Callback is formed callback = args.splice(arg, 1)[0]; //callback is formed
var json_q = formatInput.call(this_, args, 'r'); var json_q = formatInput.call(this_, args, 'r');
@@ -187,11 +146,6 @@ ImageSequencer = function ImageSequencer(options) {
return true; return true;
} }
/**
* @method loadImages
* @description Loads an image via dataURL or normal URL. Read the docs(https://github.com/publiclab/image-sequencer/blob/main/README.md) for more info.
* @returns {Null}
*/
function loadImages() { function loadImages() {
var args = []; var args = [];
var prevSteps = this.getSteps().slice(1).map(step=>step.options.name); var prevSteps = this.getSteps().slice(1).map(step=>step.options.name);
@@ -228,35 +182,17 @@ ImageSequencer = function ImageSequencer(options) {
} }
/**
* @method replaceImage
* @description Replaces the current image in the sequencer
* @param {String} selector DOM selector string for the image input
* @param {*} steps Current steps Object
* @param {Object} options
* @returns {*}
*/
function replaceImage(selector, steps, options) { function replaceImage(selector, steps, options) {
options = options || {}; options = options || {};
options.callback = options.callback || function() { }; options.callback = options.callback || function() { };
return require('./ReplaceImage')(this, selector, steps, options); return require('./ReplaceImage')(this, selector, steps, options);
} }
/** //returns the steps added
* @method getSteps
* @description Returns the current sequence of steps
* @returns {Object}
*/
function getSteps(){ function getSteps(){
return this.steps; return this.steps;
} }
/**
* @method setUI
* @description To set up a UI for ImageSequencer via different callback methods. Read the docs(https://github.com/publiclab/image-sequencer/blob/main/README.md) for more info.
* @param {Object} UI Object containing UI callback methods. Read the docs(https://github.com/publiclab/image-sequencer/blob/main/README.md) for more info.
* @returns {Null}
*/
function setUI(UI) { function setUI(UI) {
this.events = require('./ui/UserInterface')(UI); this.events = require('./ui/UserInterface')(UI);
} }
@@ -265,12 +201,6 @@ ImageSequencer = function ImageSequencer(options) {
return require('./ExportBin')(dir, this, basic, filename); return require('./ExportBin')(dir, this, basic, filename);
}; };
/**
* @method modulesInfo
* @description Returns information about the given module or all the available modules
* @param {String} name Module name
* @returns {Object}
*/
function modulesInfo(name) { function modulesInfo(name) {
var modulesdata = {}; var modulesdata = {};
if (name == 'load-image') return {}; if (name == 'load-image') return {};
@@ -292,30 +222,23 @@ ImageSequencer = function ImageSequencer(options) {
return modulesdata; return modulesdata;
} }
/**
* @method loadNewModule
* @description Adds a new local module to sequencer. Read the docs(https://github.com/publiclab/image-sequencer/blob/main/README.md) for mode info.
* @param {String} name Name of the new module
* @param {Object} options An Object containing path and info about the new module.
* @returns {Object}
*/
function loadNewModule(name, options) { function loadNewModule(name, options) {
if (!options) { if (!options) {
return this; return this;
} else if (Array.isArray(options)) { } else if (Array.isArray(options)) {
// Contains the array of module and info // contains the array of module and info
this.modules[name] = options; this.modules[name] = options;
} else if (options.func && options.info) { } else if (options.func && options.info) {
// Passed in options object // passed in options object
this.modules[name] = [ this.modules[name] = [
options.func, options.info options.func, options.info
]; ];
} else if (options.path && !this.inBrowser) { } else if (options.path && !this.inBrowser) {
// Load from path(only in node) // load from path(only in node)
const module = [ const module = [
require(`${options.path}/Module.js`), require(`${options.path}/Module.js`),
require(`${options.path}/info.json`) require(`${options.path}/info.json`)
@@ -325,13 +248,6 @@ ImageSequencer = function ImageSequencer(options) {
return this; return this;
} }
/**
* @method saveNewModule
* @description Saves a new local module to ImageSequencer
* @param {String} name Name of the new module
* @param {String} path Path to the new module
* @returns {Null}
*/
function saveNewModule(name, path) { function saveNewModule(name, path) {
if (options.inBrowser) { if (options.inBrowser) {
// Not for browser context // Not for browser context
@@ -342,13 +258,6 @@ ImageSequencer = function ImageSequencer(options) {
fs.writeFileSync('./src/Modules.js', mods); fs.writeFileSync('./src/Modules.js', mods);
} }
/**
* @method saveSequence
* @description Saves a sequence on the browser localStorage.
* @param {String} name Name for the sequence
* @param {String} sequenceString Sequence data as a string
* @returns {Null}
*/
function saveSequence(name, sequenceString) { // 4. save sequence function saveSequence(name, sequenceString) { // 4. save sequence
const sequence = str.stringToJSON(sequenceString); const sequence = str.stringToJSON(sequenceString);
// Save the given sequence string as a module // Save the given sequence string as a module
@@ -367,7 +276,7 @@ ImageSequencer = function ImageSequencer(options) {
} }
function loadModules() { function loadModules() {
// loadModules function loads the modules and saved sequences. // This function loads the modules and saved sequences
this.modules = require('./Modules'); this.modules = require('./Modules');
if (options.inBrowser) if (options.inBrowser)
this.sequences = JSON.parse(window.localStorage.getItem('sequences')); this.sequences = JSON.parse(window.localStorage.getItem('sequences'));
@@ -377,7 +286,7 @@ ImageSequencer = function ImageSequencer(options) {
return { return {
// Literals and objects //literals and objects
name: 'ImageSequencer', name: 'ImageSequencer',
options: options, options: options,
inputlog: inputlog, inputlog: inputlog,
@@ -387,7 +296,7 @@ ImageSequencer = function ImageSequencer(options) {
steps: steps, steps: steps,
image: image, image: image,
// User functions //user functions
loadImages: loadImages, loadImages: loadImages,
loadImage: loadImages, loadImage: loadImages,
addSteps: addSteps, addSteps: addSteps,
@@ -416,7 +325,7 @@ ImageSequencer = function ImageSequencer(options) {
loadModules: loadModules, loadModules: loadModules,
getSteps:getSteps, getSteps:getSteps,
// Other functions //other functions
log: log, log: log,
objTypeOf: objTypeOf, objTypeOf: objTypeOf,
copy: copy, copy: copy,

View File

@@ -11,10 +11,8 @@ module.exports = {
'canvas-resize': require('./modules/CanvasResize'), 'canvas-resize': require('./modules/CanvasResize'),
'channel': require('./modules/Channel'), 'channel': require('./modules/Channel'),
'colorbar': require('./modules/Colorbar'), 'colorbar': require('./modules/Colorbar'),
'color-halftone': require('./modules/ColorHalftone'),
'color-temperature': require('./modules/ColorTemperature'), 'color-temperature': require('./modules/ColorTemperature'),
'colormap': require('./modules/Colormap'), 'colormap': require('./modules/Colormap'),
'constrained-crop': require('./modules/ConstrainedCrop'),
'contrast': require('./modules/Contrast'), 'contrast': require('./modules/Contrast'),
'convolution': require('./modules/Convolution'), 'convolution': require('./modules/Convolution'),
'crop': require('./modules/Crop'), 'crop': require('./modules/Crop'),
@@ -32,8 +30,7 @@ module.exports = {
'grid-overlay': require('./modules/GridOverlay'), 'grid-overlay': require('./modules/GridOverlay'),
'import-image': require('./modules/ImportImage'), 'import-image': require('./modules/ImportImage'),
'minify-image': require('./modules/MinifyImage'), 'minify-image': require('./modules/MinifyImage'),
// 'invert': require('image-sequencer-invert'), 'invert': require('image-sequencer-invert'),
'invert': require('./modules/Invert'),
'ndvi': require('./modules/Ndvi'), 'ndvi': require('./modules/Ndvi'),
'ndvi-colormap': require('./modules/NdviColormap'), 'ndvi-colormap': require('./modules/NdviColormap'),
'noise-reduction': require('./modules/NoiseReduction'), 'noise-reduction': require('./modules/NoiseReduction'),

View File

@@ -45,8 +45,6 @@ function Run(ref, json_q, callback, ind, progressObj) {
// This output is accessible by UI // This output is accessible by UI
ref.steps[i].options.step.output = ref.steps[i].output.src; ref.steps[i].options.step.output = ref.steps[i].output.src;
ref.steps[i].options.step.wasmSuccess = ref.steps[i].output.wasmSuccess || false;
ref.steps[i].options.step.useWasm = ref.steps[i].output.useWasm || false;
// Tell UI that step has been drawn. // Tell UI that step has been drawn.
ref.steps[i].UI.onComplete(ref.steps[i].options.step); ref.steps[i].UI.onComplete(ref.steps[i].options.step);

View File

@@ -1,51 +1,37 @@
const _ = require('lodash'),
parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
module.exports = function AddQR(options, UI) { module.exports = function AddQR(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json')); var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.size = options.size || defaults.size;
options.qrCodeString = options.qrCodeString || 'https://github.com/publiclab/image-sequencer';
var output; var output;
getPixels = require('get-pixels'); getPixels = require('get-pixels');
function draw(input, callback, progressObj) { function draw(input, callback, progressObj) {
options.size = Number(options.size || defaults.size);
options.qrCodeString = options.qrCodeString || defaults.qrCodeString;
options.startingX = options.startingX || defaults.startingX;
options.startingY = options.startingY || defaults.startingY;
progressObj.stop(true); progressObj.stop(true);
progressObj.overrideFlag = true; progressObj.overrideFlag = true;
var step = this; var step = this;
function extraManipulation(pixels, setRenderState, generateOutput) { return getPixels(input.src, function(err, oldPixels) {
let iw = pixels.shape[0], // Width of Original Image function changePixel(r, g, b, a) {
ih = pixels.shape[1]; // Height of Original Image return [r, g, b, a];
const oldPixels = _.cloneDeep(pixels);
setRenderState(false); // Prevent rendering of final output image until extraManipulation completes.
// Parse the inputs.
parseCornerCoordinateInputs({iw, ih},
{
startingX: { valInp: options.startingX, type: 'horizontal'},
startingY: { valInp: options.startingY, type: 'vertical' },
}, function(opt, cord){
options.startingX = Math.floor(cord.startingX.valInp);
options.startingY = Math.floor(cord.startingY.valInp);
});
require('./QR')(options, pixels, oldPixels, () => {
setRenderState(true); // Allow rendering in the callback.
generateOutput();
});
} }
function output(image, datauri, mimetype, wasmSuccess) { function extraManipulation(pixels, generateOutput) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; if (err) {
console.log(err);
return;
}
require('./QR')(options, pixels, oldPixels, generateOutput);
}
function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
changePixel: changePixel,
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
@@ -53,6 +39,8 @@ module.exports = function AddQR(options, UI) {
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });
});
} }
return { return {

View File

@@ -1,37 +1,44 @@
const pixelSetter = require('../../util/pixelSetter.js'), module.exports = exports = function (options, pixels, oldPixels, callback) {
getPixels = require('get-pixels'), const pixelSetter = require('../../util/pixelSetter.js');
QRCode = require('qrcode');
module.exports = exports = function (options, pixels, oldPixels, cb) {
QRCode.toDataURL(options.qrCodeString, {width: options.size, scale: 1}, function (error, url) { var QRCode = require('qrcode');
QRCode.toDataURL(options.qrCodeString, function (err, url) {
var getPixels = require('get-pixels');
getPixels(url, function (err, qrPixels) { getPixels(url, function (err, qrPixels) {
if (err) { if (err) {
console.log('get-pixels error: ', err); console.log('Bad image path', image);
} }
const width = oldPixels.shape[0], 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],
height = oldPixels.shape[1]; height = oldPixels.shape[1];
var xe = width - options.size,
const xe = Math.min(options.startingX, width - options.size), // Starting pixel coordinates ye = height - options.size;
ye = Math.min(options.startingY, height - options.size); for (var m = 0; m < width; m++) {
for (var n = 0; n < height; n++) {
for (let x = xe; x < Math.min(xe + options.size, width); x++) { if (m >= xe && n >= ye) {
for (let y = ye; y < Math.min(ye + options.size, height); y++) { 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);
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
);
}
} }
if(cb) cb(); 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();
}); });
}); });
}; };

View File

@@ -12,16 +12,6 @@
"type": "string", "type": "string",
"desc": "input string to generate QR code", "desc": "input string to generate QR code",
"default": "https://github.com/publiclab/image-sequencer" "default": "https://github.com/publiclab/image-sequencer"
},
"startingX": {
"type": "string",
"desc": "X-position (measured from left) from where QR starts",
"default": "0"
},
"startingY": {
"type": "string",
"desc": "Y-position (measured from top) from where QR starts",
"default": "0"
} }
}, },
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#add-qr-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#add-qr-module"

View File

@@ -16,6 +16,7 @@ module.exports = function Average(options, UI) {
// do the averaging // do the averaging
function extraManipulation(pixels) { function extraManipulation(pixels) {
const $ = window.$;
var i = 0, sum = [0, 0, 0, 0]; var i = 0, sum = [0, 0, 0, 0];
while (i < pixels.data.length) { while (i < pixels.data.length) {
sum[0] += pixels.data[i++]; sum[0] += pixels.data[i++];
@@ -42,19 +43,23 @@ module.exports = function Average(options, UI) {
// report back and store average in metadata: // report back and store average in metadata:
options.step.metadata.averages = sum; options.step.metadata.averages = sum;
if (options.step.average === undefined) options.step.average = ''; // TODO: refactor into a new "display()" method as per https://github.com/publiclab/image-sequencer/issues/242
options.step.average += 'rgba(' + sum.join(', ') + ')'; if (options.step.inBrowser && options.step.ui) $(options.step.ui).find('.details').append('<p><b>Averages</b> (r, g, b, a): ' + sum.join(', ') + '</p>');
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = {
src: datauri,
format: mimetype
};
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
inBrowser: options.inBrowser,
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,

View File

@@ -3,11 +3,5 @@
"description": "Average all pixel color", "description": "Average all pixel color",
"inputs": { "inputs": {
}, },
"outputs": {
"average": {
"type": "string",
"desc": "The average value of all the pixels."
}
},
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#average-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#average-module"
} }

View File

@@ -1,10 +1,9 @@
module.exports = function Blend(options, UI, util) { module.exports = function Dynamic(options, UI, util) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json')); var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.func = options.blend || defaults.blend; options.func = options.func || defaults.blend;
options.offset = options.offset || defaults.offset; options.offset = options.offset || defaults.offset;
options.blendMode = options.blendMode || defaults.blendMode;
var output; var output;
@@ -16,6 +15,9 @@ module.exports = function Blend(options, UI, util) {
var step = this; var step = this;
// convert to runnable code:
if (typeof options.func === 'string') eval('options.func = ' + options.func);
var getPixels = require('get-pixels'); var getPixels = require('get-pixels');
// convert offset as string to int // convert offset as string to int
@@ -30,83 +32,26 @@ module.exports = function Blend(options, UI, util) {
callback(); callback();
} }
// see http://docs.gimp.org/en/gimp-concepts-layer-modes.html for other blend modes
const multiply_mode = function (i, m) {
return ~~( (i * m) / 255 );
};
const divide_mode = function (i, m) {
return ~~( (256 * i) / (m + 1) );
};
const overlay_mode = function (i, m) {
return ~~( (i / 255) * (i + ((2 * m) / 255) * (255 - i)) );
};
const screen_mode = function (i, m) {
return ~~( 255 - ((255 - m) * (255 - i)) / 255 );
};
const sof_light_mode = function (i, m) {
var Rs = screen_mode(i, m);
return ~~( ((((255 - i) * m) + Rs) * i) / 255 );
};
const color_dodge = function (i, m) {
return ~~( (256 * i) / (255 - m + 1) );
};
const burn_mode = function (i, m) {
return ~~( 255 - (256 * (255 - i)) / (m + 1));
};
const grain_extract_mode = function (i, m) {
return ~~( i - m + 128 );
};
const grain_merge_mode = function (i, m) {
return ~~( i + m - 128 );
};
getPixels(priorStep.output.src, function(err, pixels) { getPixels(priorStep.output.src, function(err, pixels) {
options.firstImagePixels = pixels; options.firstImagePixels = pixels;
// Convert to runnable code.
if (typeof options.func === 'string') eval('options.func = ' + options.func);
function changePixel(r2, g2, b2, a2, x, y) { function changePixel(r2, g2, b2, a2, x, y) {
// blend! // blend!
let p = options.firstImagePixels; let p = options.firstImagePixels;
let r1 = p.get(x, y, 0),
g1 = p.get(x, y, 1),
b1 = p.get(x, y, 2),
a1 = p.get(x, y, 3);
const blends = {
'Color Dodge': () => [color_dodge(r2, r1), color_dodge(g2, g1), color_dodge(b2, b1), 255],
'Multiply': () => [multiply_mode(r2, r1), multiply_mode(g2, g1), multiply_mode(b2, b1), multiply_mode(a2, a1)],
'Divide': () => [divide_mode(r2, r1), divide_mode(g2, g1), divide_mode(b2, b1), 255],
'Overlay': () => [overlay_mode(r2, r1), overlay_mode(g2, g1), overlay_mode(b2, b1), 255],
'Screen': () => [screen_mode(r2, r1), screen_mode(g2, g1), screen_mode(b2, b1), 255],
'Soft Light': () => [sof_light_mode(r2, r1), sof_light_mode(g2, g1), sof_light_mode(b2, b1), 255],
'Color Burn': () => [burn_mode(r2, r1), burn_mode(g2, g1), burn_mode(b2, b1), 255],
'Grain Extract': () => [grain_extract_mode(r2, r1), grain_extract_mode(g2, g1), grain_extract_mode(b2, b1), 255],
'Grain Merge': () => [grain_merge_mode(r2, r1), grain_merge_mode(g2, g1), grain_merge_mode(b2, b1), 255]
};
if(options.blendMode == 'custom')
return options.func( return options.func(
r2, g2, b2, a2, r1, g1, b1, a1 r2, g2, b2, a2,
p.get(x, y, 0),
p.get(x, y, 1),
p.get(x, y, 2),
p.get(x, y, 3)
); );
else {
return blends[options.blendMode]();
} }
} function output(image, datauri, mimetype) {
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
} }
// run PixelManipulatin on second image's pixels // run PixelManipulatin on second image's pixels

View File

@@ -1,33 +1,16 @@
{ {
"name": "blend", "name": "blend",
"description": "Blend two chosen image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2.", "description": "Blend two chosen image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2. Easier to use interfaces coming soon!",
"inputs": { "inputs": {
"offset": { "offset": {
"type": "integer", "type": "integer",
"desc": "Choose which image to blend the current image with. Two steps back is -2, three steps back is -3 etc.", "desc": "Choose which image to blend the current image with. Two steps back is -2, three steps back is -3 etc.",
"default": -2 "default": -2
}, },
"blendMode": {
"type": "select",
"desc": "Name of the Blend Mode to use",
"default": "custom",
"values": [
"custom",
"Multiply",
"Divide",
"Overlay",
"Screen",
"Soft Light",
"Color Burn",
"Color Dodge",
"Grain Extract",
"Grain Merge"
]
},
"blend": { "blend": {
"type": "string", "type": "string",
"desc": "Function to use to blend the two images.", "desc": "Function to use to blend the two images.",
"default": "function(r1, g1, b1, a1, r2, g2, b2, a2, x, y) { return [ r2, g2, b2, a2 ] }" "default": "function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }"
} }
}, },
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blend-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blend-module"

View File

@@ -1,10 +1,17 @@
module.exports = function(pixels){ module.exports = function(pixels, options, priorStep){
var $ = require('jquery'); // to make Blob-analysis work for node.js
var img = $(priorStep.imgElement);
if(Object.keys(img).length === 0){
img = $(priorStep.options.step.imgElement);
}
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
canvas.width = pixels.shape[0]; canvas.width = pixels.shape[0];
canvas.height = pixels.shape[1]; canvas.height = pixels.shape[1];
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.shape[0], pixels.shape[1]), 0, 0); ctx.drawImage(img[0], 0, 0);
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -18,26 +25,26 @@ module.exports = function(pixels){
let unknown = new cv.Mat(); let unknown = new cv.Mat();
let markers = new cv.Mat(); let markers = new cv.Mat();
// Gray and Threshold the image // gray and threshold image
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU); cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);
// Get background // get background
let M = cv.Mat.ones(3, 3, cv.CV_8U); let M = cv.Mat.ones(3, 3, cv.CV_8U);
cv.erode(gray, gray, M); cv.erode(gray, gray, M);
cv.dilate(gray, opening, M); cv.dilate(gray, opening, M);
cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3); cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3);
// Distance transform // distance transform
cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5); cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5);
cv.normalize(distTrans, distTrans, 1, 0, cv.NORM_INF); cv.normalize(distTrans, distTrans, 1, 0, cv.NORM_INF);
// Get foreground // get foreground
cv.threshold(distTrans, imageFg, 0.7 * 1, 255, cv.THRESH_BINARY); cv.threshold(distTrans, imageFg, 0.7 * 1, 255, cv.THRESH_BINARY);
imageFg.convertTo(imageFg, cv.CV_8U, 1, 0); imageFg.convertTo(imageFg, cv.CV_8U, 1, 0);
cv.subtract(imageBg, imageFg, unknown); cv.subtract(imageBg, imageFg, unknown);
// Get connected components markers // get connected components markers
cv.connectedComponents(imageFg, markers); cv.connectedComponents(imageFg, markers);
for (let i = 0; i < markers.rows; i++) { for (let i = 0; i < markers.rows; i++) {
for (let j = 0; j < markers.cols; j++) { for (let j = 0; j < markers.cols; j++) {
@@ -51,13 +58,13 @@ module.exports = function(pixels){
cv.cvtColor(src, src, cv.COLOR_RGBA2RGB, 0); cv.cvtColor(src, src, cv.COLOR_RGBA2RGB, 0);
cv.watershed(src, markers); cv.watershed(src, markers);
// Grow barriers // draw barriers
for (let i = 0; i < markers.rows; i++) { for (let i = 0; i < markers.rows; i++) {
for (let j = 0; j < markers.cols; j++) { for (let j = 0; j < markers.cols; j++) {
if (markers.intPtr(i, j)[0] == -1) { if (markers.intPtr(i, j)[0] == -1) {
src.ucharPtr(i, j)[0] = 255; // Red src.ucharPtr(i, j)[0] = 255; // R
src.ucharPtr(i, j)[1] = 0; // Green src.ucharPtr(i, j)[1] = 0; // G
src.ucharPtr(i, j)[2] = 0; // Blue src.ucharPtr(i, j)[2] = 0; // B
} }
} }
} }

View File

@@ -10,14 +10,17 @@ module.exports = function BlobAnalysis(options, UI){
var step = this; var step = this;
var priorStep = this.getStep(-1); // get the previous step to process it
function extraManipulation(pixels){ function extraManipulation(pixels){
pixels = require('./BlobAnalysis')(pixels); pixels = require('./BlobAnalysis')(pixels, options, priorStep);
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype){
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
step.output = { src: datauri, format: mimetype};
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -2,6 +2,5 @@
"name": "Blob Analysis", "name": "Blob Analysis",
"description": "Blob/Region identification for microscopic images.", "description": "Blob/Region identification for microscopic images.",
"inputs": {}, "inputs": {},
"requires": ["webgl", "browser"],
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blob-analysis" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blob-analysis"
} }

View File

@@ -1,3 +1,40 @@
module.exports = exports = function(pixels, blur) {
const pixelSetter = require('../../util/pixelSetter.js');
let kernel = kernelGenerator(blur),
pixs = {
r: [],
g: [],
b: [],
};
for (let y = 0; y < pixels.shape[1]; y++){
pixs.r.push([]);
pixs.g.push([]);
pixs.b.push([]);
for (let x = 0; x < pixels.shape[0]; x++){
pixs.r[y].push(pixels.get(x, y, 0));
pixs.g[y].push(pixels.get(x, y, 1));
pixs.b[y].push(pixels.get(x, y, 2));
}
}
const convolve = require('../_nomodule/gpuUtils').convolve;
const conPix = convolve([pixs.r, pixs.g, pixs.b], kernel);
for (let y = 0; y < pixels.shape[1]; y++){
for (let x = 0; x < pixels.shape[0]; x++){
var pixelvalue = [Math.max(0, Math.min(conPix[0][y][x], 255)),
Math.max(0, Math.min(conPix[1][y][x], 255)),
Math.max(0, Math.min(conPix[2][y][x], 255))];
pixelSetter(x, y, pixelvalue, pixels);
}
}
return pixels;
//Generates a 5x5 Gaussian kernel //Generates a 5x5 Gaussian kernel
function kernelGenerator(sigma = 1) { function kernelGenerator(sigma = 1) {
@@ -25,41 +62,4 @@ function kernelGenerator(sigma = 1) {
return kernel; return kernel;
} }
module.exports = exports = function(pixels, blur) {
const pixelSetter = require('../../util/pixelSetter.js');
let kernel = kernelGenerator(blur), // Generate the Gaussian kernel based on the sigma input.
pixs = { // Separates the rgb channel pixels to convolve on the GPU.
r: [],
g: [],
b: [],
};
for (let y = 0; y < pixels.shape[1]; y++){
pixs.r.push([]);
pixs.g.push([]);
pixs.b.push([]);
for (let x = 0; x < pixels.shape[0]; x++){
pixs.r[y].push(pixels.get(x, y, 0));
pixs.g[y].push(pixels.get(x, y, 1));
pixs.b[y].push(pixels.get(x, y, 2));
}
}
const convolve = require('../_nomodule/gpuUtils').convolve; // GPU convolution function.
const conPix = convolve([pixs.r, pixs.g, pixs.b], kernel); // Convolves the pixels (all channels separately) on the GPU.
for (let y = 0; y < pixels.shape[1]; y++){
for (let x = 0; x < pixels.shape[0]; x++){
var pixelvalue = [Math.max(0, Math.min(conPix[0][y][x], 255)),
Math.max(0, Math.min(conPix[1][y][x], 255)),
Math.max(0, Math.min(conPix[2][y][x], 255))];
pixelSetter(x, y, pixelvalue, pixels); // Sets the image pixels according to the blurred values.
}
}
return pixels;
}; };

View File

@@ -20,14 +20,16 @@ module.exports = function Blur(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
inBrowser: options.inBrowser,
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,

View File

@@ -31,8 +31,11 @@ module.exports = function Brightness(options, UI) {
return [r, g, b, a]; return [r, g, b, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -35,8 +35,11 @@ module.exports = function canvasResize(options, UI) {
return newPixels; return newPixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -22,6 +22,5 @@
"desc": "Y-cord of the top left corner of the image on the canvas", "desc": "Y-cord of the top left corner of the image on the canvas",
"default": 500 "default": 500
} }
}, }
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#canvas-resize-module"
} }

View File

@@ -22,8 +22,11 @@ module.exports = function Channel(options, UI) {
if (options.channel === 'blue') return [0, 0, b, a]; if (options.channel === 'blue') return [0, 0, b, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -1,135 +0,0 @@
/*
* Generate halftone versions of CMYK channels and blend them with varying rotations as in analog print color separation processes.
* Simulates a CMYK halftone rendering of the image by multiplying pixel values with a four rotated 2D sine wave patterns, one each for cyan, magenta, yellow, and black.
* http://evanw.github.io/glfx.js/docs/#colorHalftone
*/
module.exports = function ColorHalftone(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var output;
var fx = require('glfx');
var dataURItoBlob = function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: mimeString });
};
var canvasToBlobUrl = function canvasToBlobUrl(canvas) {
var blob = dataURItoBlob(canvas.toDataURL('image/png'));
return window.URL.createObjectURL(blob);
};
var colorHalftone = function colorHalftone(id, options, download) {
// try to create a WebGL canvas (will fail if WebGL isn't supported)
try {
var canvas = fx.canvas(1500, 1500);
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
var imageEl = document.getElementById(id);
var image = new Image();
image.onload = function() {
var texture = canvas.texture(image);
canvas.draw(texture,
image.width, // * ratio,
image.height// * ratio
);
canvas.colorHalftone(
image.width / 2,
image.height / 2,
parseFloat(options.angle),
parseFloat(options.size)
).update();
var burl = canvasToBlobUrl(canvas);
if (download) {
window.open(burl);
} else { // replace the image
// keep non-blob version in case we have to fall back:
// image.src = canvas.toDataURL('image/png');
// window.location = canvas.toDataURL('image/png');
imageEl.src = burl;
}
};
$(image).hide();
image.src = imageEl.src;
};
function draw(input, callback) {
var step = this;
options.angle = options.angle || defaults.angle;
options.size = options.size || defaults.size;
if (!options.inBrowser) {
// this.output = input;
// callback();
require('../_nomodule/gl-context')(input, callback, step, options);
}
else {
var image = document.createElement('img');
image.onload = () => {
colorHalftone(
'img',
options
);
image.onload = () => {
var canvas = document.createElement('canvas');
canvas.width = image.naturalWidth; // or 'width' if you want a special/scaled size
canvas.height = image.naturalHeight; // or 'height' if you want a special/scaled size
canvas.getContext('2d').drawImage(image, 0, 0);
step.output = { src: canvas.toDataURL('image/png'), format: 'png' };
image.remove();
callback();
};
};
image.src = input.src;
image.id = 'img';
document.body.appendChild(image);
}
}
return {
options: options,
draw: draw,
output: output,
UI: UI
};
};

View File

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

View File

@@ -1,22 +0,0 @@
{
"name": "color-halftone",
"requires": ["webgl"],
"description": "Generate halftone versions of CMYK channels and blend them with varying rotations as in analog print color separation processes.",
"inputs": {
"angle": {
"type": "float",
"desc": "angle of rotation of the halftone patterns in radians",
"default": "0.25",
"min": "0",
"max": "1.57"
},
"size": {
"type": "integer",
"desc": "the diameter of a dot in pixels",
"default": "4",
"min": "3",
"max": "20"
}
},
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#color-halftone-module"
}

View File

@@ -1,15 +1,12 @@
module.exports = function ColorTemperature(options, UI) { module.exports = function ColorTemperature(options, UI) {
const pixelSetter = require('../../util/pixelSetter.js'); const pixelSetter = require('../../util/pixelSetter.js');
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var output; var output;
function draw(input, callback, progressObj) { function draw(input, callback, progressObj) {
options.temperature = options.temperature || defaults.temperature; options.temperature = (options.temperature > '40000') ? '40000' : options.temperature;
options.temperature = (options.temperature > 40000) ? 40000 : options.temperature;
options.temperature = (options.temperature < 0) ? 0 : options.temperature;
progressObj.stop(true); progressObj.stop(true);
progressObj.overrideFlag = true; progressObj.overrideFlag = true;
@@ -55,8 +52,10 @@ module.exports = function ColorTemperature(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

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

View File

@@ -12,8 +12,7 @@
*/ */
module.exports = function Colormap(value, options) { module.exports = function Colormap(value, options) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json')); options.colormap = options.colormap || colormaps.default;
options.colormap = options.colormap || defaults.colormap;
// if a lookup table is provided as an array: // if a lookup table is provided as an array:
if(typeof(options.colormap) == 'object') if(typeof(options.colormap) == 'object')
colormapFunction = colormap(options.colormap); colormapFunction = colormap(options.colormap);

View File

@@ -16,10 +16,12 @@ module.exports = function Colormap(options, UI) {
return [res[0], res[1], res[2], 255]; return [res[0], res[1], res[2], 255];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
}
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,

View File

@@ -1,56 +0,0 @@
/*
* Crops an Image on the basis of the ratio provided
*/
module.exports = function ConstrainedCrop(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var output;
function draw(input, callback) {
var step = this,
startingX = Number(options.startingX || defaults.startingX),
startingY = Number(options.startingY || defaults.startingY),
aspectRatio = (options.aspectRatio || defaults.aspectRatio).split(':'),
widthRatio = Number(aspectRatio[0]),
heightRatio = Number(aspectRatio[1]);
function extraManipulation(pixels) {
var width = pixels.shape[0],
height = pixels.shape[1];
var endX, endY;
if(((width - startingX) / widthRatio) * heightRatio <= (height - startingY)) {
endX = width;
endY = (((width - startingX) / widthRatio) * heightRatio) + startingY;
}
else {
endX = (((height - startingY) / heightRatio) * widthRatio) + startingX;
endY = height;
}
const newPixels = require('../Crop/Crop')(pixels, {'x': startingX, 'y': startingY, 'w': endX - startingX, 'h': endY - startingY}, function() {
});
return newPixels;
}
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
return require('../_nomodule/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 {
options: options,
draw: draw,
output: output,
UI: UI
};
};

View File

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

View File

@@ -1,23 +0,0 @@
{
"name": "constrained-crop",
"description": "Crops an image in a particular aspect-ratio",
"inputs": {
"startingX": {
"type": "integer",
"desc": "X-position (measured from left) from where cropping starts",
"default": 0
},
"startingY": {
"type": "integer",
"desc": "Y-position (measured from top) from where cropping starts",
"default": 0
},
"aspectRatio":{
"type": "string",
"desc": "Enter aspect ratio in following format width:height",
"default": "1:1"
}
},
"docs-link":""
}

View File

@@ -0,0 +1,30 @@
var _ = require('lodash');
const pixelSetter = require('../../util/pixelSetter.js');
module.exports = exports = function(pixels, contrast) {
let oldpix = _.cloneDeep(pixels);
contrast = Number(contrast);
if (contrast < -100) contrast = -100;
if (contrast > 100) contrast = 100;
contrast = (100.0 + contrast) / 100.0;
contrast *= contrast;
for (let i = 0; i < pixels.shape[0]; i++) {
for (let j = 0; j < pixels.shape[1]; j++) {
var rgbarray = [oldpix.get(i, j, 0) / 255.0, oldpix.get(i, j, 1) / 255.0, oldpix.get(i, j, 2) / 255.0];
for(var idx = 0;idx < 3;idx++){
rgbarray[idx] -= 0.5;
rgbarray[idx] *= contrast;
rgbarray[idx] += 0.5;
rgbarray[idx] *= 255;
if (rgbarray[idx] < 0) rgbarray[idx] = 0;
if (rgbarray[idx] > 255) rgbarray[idx] = 255;
}
pixelSetter(i, j, rgbarray, pixels);
}
}
return pixels;
};

View File

@@ -15,41 +15,25 @@ module.exports = function Contrast(options, UI) {
var step = this; var step = this;
let contrast = options.contrast; function extraManipulation(pixels) {
pixels = require('./Contrast')(pixels, options.contrast);
contrast = Number(contrast); return pixels;
if (contrast < -100) contrast = -100;
if (contrast > 100) contrast = 100;
contrast = (100.0 + contrast) / 100.0;
contrast *= contrast;
function changeContrast(p){
p -= 0.5;
p *= contrast;
p += 0.5;
p *= 255;
p = Math.max(0, p);
p = Math.min(p, 255);
return p;
} }
function changePixel(r, g, b, a) { function output(image, datauri, mimetype) {
return [changeContrast(r / 255), changeContrast(g / 255), changeContrast(b / 255), a]; // This output is accessible by Image Sequencer
} step.output = { src: datauri, format: mimetype };
function output(image, datauri, mimetype, wasmSuccess) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
changePixel: changePixel, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
callback: callback, callback: callback,
inBrowser: options.inBrowser,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -19,8 +19,10 @@ module.exports = function Convolution(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -29,7 +31,6 @@ module.exports = function Convolution(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -1,71 +1,61 @@
const ndarray = require('ndarray'), module.exports = function Crop(input, options, callback) {
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 defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.x = options.x || defaults.x; var getPixels = require('get-pixels'),
options.y = options.y || defaults.y; savePixels = require('save-pixels');
options.w = options.w || defaults.w; options.x = parseInt(options.x) || defaults.x;
options.h = options.h || defaults.h; options.y = parseInt(options.y) || defaults.y;
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; 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(','); var newarray = Uint8Array.from(array);
pixels.data = newarray;
pixels.shape = [w, h, 4];
pixels.stride[1] = 4 * w;
let iw = pixels.shape[0], // Width of Original Image options.format = input.format;
ih = pixels.shape[1], // Height of Original Image
offsetX,
offsetY,
w,
h;
// Parse the inputs var chunks = [];
parseCornerCoordinateInputs({iw, ih}, var totalLength = 0;
{ var r = savePixels(pixels, options.format);
x: { valInp: options.x, type: 'horizontal' },
y: { valInp: options.y, type: 'vertical' }, r.on('data', function(chunk){
w: { valInp: options.w, type: 'horizontal' }, totalLength += chunk.length;
h: { valInp: options.h, type: 'vertical' }, chunks.push(chunk);
}, 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]); r.on('end', function(){
var data = Buffer.concat(chunks, totalLength).toString('base64');
for (let x = 0; x < w; x++) { var datauri = 'data:image/' + options.format + ';base64,' + data;
for (let y = 0; y < h; y++) { callback(datauri, options.format);
pixelSetter(x, y, bg, newPixels); // Set the background color });
} });
}
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;
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
}
}
if (cb) cb();
return newPixels;
}; };

View File

@@ -1,4 +1,3 @@
const pixelManipulation = require('../_nomodule/PixelManipulation');
/* /*
* Image Cropping module * Image Cropping module
* Usage: * Usage:
@@ -27,37 +26,54 @@ module.exports = function CropModule(options, UI) {
var step = this; 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; options.step.input = input.src;
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
function extraManipulation(pixels) { //parse the inputs
const newPixels = require('./Crop')(pixels, options, function() { parseCornerCoordinateInputs(options, {
// We should do this via event/listener: 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:
if (ui && ui.hide) ui.hide(); if (ui && ui.hide) ui.hide();
// Start custom UI setup (draggable UI) // start custom UI setup (draggable UI)
// Only once we have an input image // only once we have an input image
if (setupComplete === false && options.step.inBrowser && !options.noUI) { if (setupComplete === false && options.step.inBrowser && !options.noUI) {
setupComplete = true; setupComplete = true;
ui.setup(); ui.setup();
} }
});
return newPixels;
}
function output(image, datauri, mimetype, wasmSuccess) { // Tell Image Sequencer that step has been drawn
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; callback();
}
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 { return {

View File

@@ -59,6 +59,12 @@ module.exports = function CropModuleUi(step, ui) {
]; ];
} }
function remove() {
$(imgEl()).imgAreaSelect({
remove: true
});
}
function hide() { function hide() {
// then hide the draggable UI // then hide the draggable UI
$(imgEl()).imgAreaSelect({ $(imgEl()).imgAreaSelect({
@@ -86,6 +92,7 @@ module.exports = function CropModuleUi(step, ui) {
return { return {
setup: setup, setup: setup,
remove: remove,
hide: hide hide: hide
}; };
}; };

View File

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

View File

@@ -28,16 +28,18 @@ module.exports = function DoNothing(options, UI) {
options.step.qrval = (decoded) ? decoded.data : 'undefined'; options.step.qrval = (decoded) ? decoded.data : 'undefined';
}); });
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; // This output is accessible by Image Sequencer
step.output = {
src: datauri,
format: mimetype
};
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -15,8 +15,10 @@ module.exports = function Dither(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; // This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -25,7 +27,6 @@ module.exports = function Dither(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -4,24 +4,22 @@ module.exports = exports = function(pixels, options){
options.startingX = options.startingX || defaults.startingX; options.startingX = options.startingX || defaults.startingX;
options.startingY = options.startingY || defaults.startingY; options.startingY = options.startingY || defaults.startingY;
var ox = Number(options.startingX), var ox = Number(options.startingX),
oy = Number(options.startingY), oy = Number(options.startingY),
iw = pixels.shape[0], iw = pixels.shape[0],
ih = pixels.shape[1], ih = pixels.shape[1],
thickness = Number(options.thickness) || defaults.thickness, thickness = Number(options.thickness) || defaults.thickness,
ex = Number(options.endX || defaults.endX) - thickness || iw - 1, ex = options.endX = Number(options.endX) - thickness || iw - 1,
ey = Number(options.endY || defaults.endY) - thickness || ih - 1, ey = options.endY = Number(options.endY) - thickness || ih - 1,
color = options.color || defaults.color; 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(','); color = color.split(',');
var drawSide = function(startX, startY, endX, endY){ var drawSide = function(startX, startY, endX, endY){
for (var n = startX; n <= endX + thickness; n++){ for (var n = startX; n <= endX + thickness; n++){
for (var k = startY; k <= endY + thickness; k++){ 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

@@ -19,8 +19,10 @@ module.exports = function DrawRectangle(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -30,7 +32,6 @@ module.exports = function DrawRectangle(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -17,13 +17,13 @@
"endX":{ "endX":{
"type": "integer", "type": "integer",
"desc": "last x position of the rectangle", "desc": "last x position of the rectangle",
"default": 10 "default": "width"
}, },
"endY":{ "endY":{
"type": "integer", "type": "integer",
"desc": "last y position of the rectangle", "desc": "last y position of the rectangle",
"default": 10 "default": "height"
}, },
"thickness":{ "thickness":{

View File

@@ -1,5 +1,5 @@
module.exports = function Dynamic(options, UI) { module.exports = function Dynamic(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var output; var output;
// This function is called on every draw. // This function is called on every draw.
@@ -10,12 +10,13 @@ module.exports = function Dynamic(options, UI) {
var step = this; var step = this;
options.red = options.red || defaults.red; // start with monochrome, but if options.red, options.green, and options.blue are set, accept them too
options.blue = options.blue || defaults.blue; options.monochrome = options.monochrome || '(R+G+B)/3';
options.green = options.green || defaults.green;
function generator(expression) { function generator(expression) {
var func = 'f = function (r, g, b, a) { var R = r, G = g, B = b, A = a; return ' + expression + ';}'; var func = 'f = function (r, g, b, a) { var R = r, G = g, B = b, A = a;';
func = func + 'return ';
func = func + expression + '}';
var f; var f;
eval(func); eval(func);
return f; return f;
@@ -24,12 +25,9 @@ module.exports = function Dynamic(options, UI) {
var channels = ['red', 'green', 'blue', 'alpha']; var channels = ['red', 'green', 'blue', 'alpha'];
channels.forEach(function(channel) { channels.forEach(function(channel) {
if (channel === 'alpha'){ if (options.hasOwnProperty(channel)) options[channel + '_function'] = generator(options[channel]);
options['alpha_function'] = function() { return 255; }; else if (channel === 'alpha') options['alpha_function'] = function() { return 255; };
} else options[channel + '_function'] = generator(options.monochrome);
else{
options[channel + '_function'] = generator(options[channel]);
}
}); });
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
@@ -73,10 +71,12 @@ module.exports = function Dynamic(options, UI) {
} }
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
}
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
}
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,

View File

@@ -16,6 +16,11 @@
"type": "string", "type": "string",
"desc": "Expression to return for blue channel with R, G, B, and A inputs", "desc": "Expression to return for blue channel with R, G, B, and A inputs",
"default": "b" "default": "b"
},
"monochrome (fallback)": {
"type": "string",
"desc": "Expression to return with R, G, B, and A inputs; fallback for other channels if none provided",
"default": "r + g + b"
} }
}, },
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#dynamic-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#dynamic-module"

View File

@@ -1,8 +1,6 @@
// Read More: https://en.wikipedia.org/wiki/Canny_edge_detector // Define kernels for the sobel filter
const pixelSetter = require('../../util/pixelSetter.js'); const pixelSetter = require('../../util/pixelSetter.js');
// Define kernels for the sobel filter.
const kernelx = [ const kernelx = [
[-1, 0, 1], [-1, 0, 1],
[-2, 0, 2], [-2, 0, 2],
@@ -23,7 +21,7 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
grads.push([]); grads.push([]);
angles.push([]); angles.push([]);
for (var y = 0; y < pixels.shape[1]; y++) { for (var y = 0; y < pixels.shape[1]; y++) {
var result = sobelFilter( // Convolves the sobel filter on every pixel var result = sobelFilter(
pixels, pixels,
x, x,
y y
@@ -34,47 +32,28 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
angles.slice(-1)[0].push(result.angle); angles.slice(-1)[0].push(result.angle);
} }
} }
nonMaxSupress(pixels, grads, angles); // Non Maximum Suppression: Filter fine edges. nonMaxSupress(pixels, grads, angles);
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels); // Double Threshold: Categorizes edges into strong and weak edges based on two thresholds. doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels);
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels); // Optional Hysteresis (very slow) to minimize edges generated due to noise. if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels);
strongEdgePixels.forEach(pixel => preserve(pixels, pixel)); // Makes the strong edges White. strongEdgePixels.forEach(pixel => preserve(pixels, pixel));
weakEdgePixels.forEach(pixel => supress(pixels, pixel)); // Makes the weak edges black(bg color) after filtering. weakEdgePixels.forEach(pixel => supress(pixels, pixel));
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel)); // Makes the rest of the image black. pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel));
return pixels; return pixels;
}; };
/**
* @method supress
* @description Supresses (fills with background color) the specified (non-edge)pixel.
* @param {Object} pixels ndarry of pixels
* @param {Float32Array} pixel Pixel coordinates
* @returns {Null}
*/
function supress(pixels, pixel) { function supress(pixels, pixel) {
pixelSetter(pixel[0], pixel[1], [0, 0, 0, 255], pixels); pixelSetter(pixel[0], pixel[1], [0, 0, 0, 255], pixels);
} }
/**
* @method preserve
* @description Preserve the specified pixel(of an edge).
* @param {Object} pixels ndarray of pixels
* @param {*} pixel Pixel coordinates
* @returns {Null}
*/
function preserve(pixels, pixel) { function preserve(pixels, pixel) {
pixelSetter(pixel[0], pixel[1], [255, 255, 255, 255], pixels); pixelSetter(pixel[0], pixel[1], [255, 255, 255, 255], pixels);
} }
/** // sobelFilter function that convolves sobel kernel over every pixel
* @method sobelFiler
* @description Runs the sobel filter on the specified and neighbouring pixels.
* @param {Object} pixels ndarray of pixels
* @param {Number} x x-coordinate of the pixel
* @param {Number} y y-coordinate of the pixel
* @returns {Object} Object containing the gradient and angle.
*/
function sobelFilter(pixels, x, y) { function sobelFilter(pixels, x, y) {
let val = pixels.get(x, y, 0), let val = pixels.get(x, y, 0),
gradX = 0.0, gradX = 0.0,
@@ -86,8 +65,8 @@ function sobelFilter(pixels, x, y) {
let xn = x + a - 1, let xn = x + a - 1,
yn = y + b - 1; yn = y + b - 1;
if (isOutOfBounds(pixels, xn, yn)) { // Fallback for coordinates which lie outside the image. if (isOutOfBounds(pixels, xn, yn)) {
gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b]; // Fallback to nearest pixel gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b];
gradY += pixels.get(xn + 1, yn + 1, 0) * kernely[a][b]; gradY += pixels.get(xn + 1, yn + 1, 0) * kernely[a][b];
} }
else { else {
@@ -105,12 +84,6 @@ function sobelFilter(pixels, x, y) {
}; };
} }
/**
* @method categorizeAngle
* @description Categorizes the given angle into 4 catagories according to the Category Map given below.
* @param {Number} angle Angle in degrees
* @returns {Number} Category number of the given angle
*/
function categorizeAngle(angle){ function categorizeAngle(angle){
if ((angle >= -22.5 && angle <= 22.5) || (angle < -157.5 && angle >= -180)) return 1; if ((angle >= -22.5 && angle <= 22.5) || (angle < -157.5 && angle >= -180)) return 1;
else if ((angle >= 22.5 && angle <= 67.5) || (angle < -112.5 && angle >= -157.5)) return 2; else if ((angle >= 22.5 && angle <= 67.5) || (angle < -112.5 && angle >= -157.5)) return 2;
@@ -125,25 +98,17 @@ function categorizeAngle(angle){
*/ */
} }
/**
* @method isOutOfBounds
* @description Checks whether the given coordinates lie outside the bounds of the image. Used for error handling in convolution.
* @param {Object} pixels ndarray of pixels
* @param {*} x x-coordinate of the pixel
* @param {*} y y-coordinate of the pixel
* @returns {Boolean} True if the given coordinates are out of bounds.
*/
function isOutOfBounds(pixels, x, y){ function isOutOfBounds(pixels, x, y){
return ((x < 0) || (y < 0) || (x >= pixels.shape[0]) || (y >= pixels.shape[1])); return ((x < 0) || (y < 0) || (x >= pixels.shape[0]) || (y >= pixels.shape[1]));
} }
const removeElem = (arr = [], elem) => { // Removes the specified element from the given array. const removeElem = (arr = [], elem) => {
return arr = arr.filter((arrelem) => { return arr = arr.filter((arrelem) => {
return arrelem !== elem; return arrelem !== elem;
}); });
}; };
// Non Maximum Supression without interpolation. // Non Maximum Supression without interpolation
function nonMaxSupress(pixels, grads, angles) { function nonMaxSupress(pixels, grads, angles) {
angles = angles.map((arr) => arr.map(convertToDegrees)); angles = angles.map((arr) => arr.map(convertToDegrees));
@@ -153,7 +118,7 @@ function nonMaxSupress(pixels, grads, angles) {
let angleCategory = categorizeAngle(angles[x][y]); let angleCategory = categorizeAngle(angles[x][y]);
if (!isOutOfBounds(pixels, x - 1, y - 1) && !isOutOfBounds(pixels, x + 1, y + 1)){ if (!isOutOfBounds(pixels, x - 1, y - 1) && !isOutOfBounds(pixels, x + 1, y + 1)){
switch (angleCategory){ // Non maximum suppression according to angle category switch (angleCategory){
case 1: case 1:
if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) { if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) {
pixelsToBeSupressed.push([x, y]); pixelsToBeSupressed.push([x, y]);
@@ -182,24 +147,17 @@ function nonMaxSupress(pixels, grads, angles) {
} }
} }
} }
// Converts radians to degrees
/**
* @method convertToDegrees
* @description Converts the given angle(in radians) to degrees.
* @param {Number} radians Angle in radians
* @returns {Number} Angle in degrees
*/
var convertToDegrees = radians => (radians * 180) / Math.PI; var convertToDegrees = radians => (radians * 180) / Math.PI;
// Finds the max value in a 2d array like grads. // Finds the max value in a 2d array like grads
var findMaxInMatrix = arr => Math.max(...arr.map(el => el.map(val => val ? val : 0)).map(el => Math.max(...el))); var findMaxInMatrix = arr => Math.max(...arr.map(el => el.map(val => val ? val : 0)).map(el => Math.max(...el)));
// Applies the double threshold to the image. // Applies the double threshold to the image
function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) { function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) {
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio, // High Threshold relative to the strongest edge const highThreshold = findMaxInMatrix(grads) * highThresholdRatio,
lowThreshold = highThreshold * lowThresholdRatio; // Low threshold relative to high threshold lowThreshold = highThreshold * lowThresholdRatio;
for (let x = 0; x < pixels.shape[0]; x++) { for (let x = 0; x < pixels.shape[0]; x++) {
for (let y = 0; y < pixels.shape[1]; y++) { for (let y = 0; y < pixels.shape[1]; y++) {
@@ -220,12 +178,6 @@ function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, s
} }
} }
/**
* @method hysteresis
* @description Filters weak edge pixels that are not connected to a strong edge pixel.
* @param {Float32array} strongEdgePixels 2D array of strong edge pixel coordinates
* @param {*} weakEdgePixels 2D array of weak edge pixel coordinated
*/
function hysteresis(strongEdgePixels, weakEdgePixels){ function hysteresis(strongEdgePixels, weakEdgePixels){
strongEdgePixels.forEach(pixel => { strongEdgePixels.forEach(pixel => {
let x = pixel[0], let x = pixel[0],

View File

@@ -1,8 +1,5 @@
const Blur = require('../Blur/Blur');
/* /*
* Detect Edges in an Image * Detect Edges in an Image
* Uses Canny method for the same
* Read more: https://en.wikipedia.org/wiki/Canny_edge_detector
*/ */
module.exports = function edgeDetect(options, UI) { module.exports = function edgeDetect(options, UI) {
@@ -22,19 +19,27 @@ module.exports = function edgeDetect(options, UI) {
var step = this; var step = this;
// Makes the image greyscale // Blur the image
const internalSequencer = ImageSequencer({ inBrowser: false, ui: false });
return internalSequencer.loadImage(input.src, function() {
internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]);
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
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a]; return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
} }
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution function extraManipulation() {
function extraManipulation(pixels) {
const blurPixels = Blur(pixels, options.blur);
return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis); return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -48,6 +53,9 @@ module.exports = function edgeDetect(options, UI) {
callback: callback, callback: callback,
useWasm: options.useWasm useWasm: options.useWasm
}); });
});
});
});
} }
return { return {

View File

@@ -1,6 +1,6 @@
{ {
"name": "edge-detect", "name": "edge-detect",
"description": "Edge Detect module detects edges using the Canny method, which first blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. [Read more](https://en.wikipedia.org/wiki/Canny_edge_detector)", "description": "This module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge.<a href='https://en.wikipedia.org/wiki/Canny_edge_detector'> Read more. </a>",
"inputs": { "inputs": {
"blur": { "blur": {
"type": "float", "type": "float",

View File

@@ -24,8 +24,11 @@ module.exports = function Exposure(options, UI) {
return [r, g, b, a]; return [r, g, b, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -11,5 +11,5 @@
"step": 0.05 "step": 0.05
} }
}, },
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#exposure-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md"
} }

View File

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

View File

@@ -21,8 +21,10 @@ module.exports = function Gamma(options, UI) {
return [r, g, b, a]; return [r, g, b, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -1,57 +1,55 @@
const pixelSetter = require('../../util/pixelSetter.js'), module.exports = function Invert(options, UI) {
pixelManipulation = require('../_nomodule/PixelManipulation'); const pixelSetter = require('../../util/pixelSetter.js');
module.exports = function Gradient(options, UI) {
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.gradientType = options.gradientType || defaults.gradientType;
var output; var output;
// The function which is called on every draw. // The function which is called on every draw.
function draw(input, callback) { function draw(input, callback) {
var getPixels = require('get-pixels');
var savePixels = require('save-pixels');
var step = this; var step = this;
function output(image, datauri, mimetype, wasmSuccess) { getPixels(input.src, function(err, pixels) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
if (err) {
console.log('Bad Image path');
return;
} }
function extraManipulation(pixels) { var width = pixels.shape[0];
const [w, h] = pixels.shape;
if (options.gradientType === 'linear') {
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
let val = (i / w) * 255;
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); pixelSetter(i, j, [val, val, val, 255], pixels);
}
}
}
else {
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
var distX = Math.abs(w / 2 - i);
var distY = Math.abs(h / 2 - j);
var distance = Math.sqrt(Math.pow(distX, 2) + Math.pow(distY, 2));
val = 255 * (distance / pixels.shape[0]);
pixelSetter(i, j, [val, val, val, 255], pixels);
}
}
}
return pixels;
} }
}
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, input.format, { quality: 100 });
return pixelManipulation(input, { r.on('data', function(chunk) {
output, totalLength += chunk.length;
extraManipulation, chunks.push(chunk);
callback,
format: input.format,
image: options.image,
inBrowser: options.inBrowser,
useWasm:options.useWasm
}); });
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) {
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
}
} }
return { return {

View File

@@ -1,13 +1,6 @@
{ {
"name": "gradient", "name": "gradient",
"description": "Gives a gradient of the image", "description": "Gives a gradient of the image",
"inputs": { "inputs": {},
"gradientType": {
"type": "select",
"desc": "Choose between linear or circular gradient",
"default": "linear",
"values": ["linear", "circular"]
}
},
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#gradient-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#gradient-module"
} }

View File

@@ -15,8 +15,11 @@ module.exports = function GridOverlay(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -11,7 +11,7 @@ module.exports = function Channel(options, UI) {
const pixelSetter = require('../../util/pixelSetter.js'); const pixelSetter = require('../../util/pixelSetter.js');
options.gradient = options.gradient || defaults.gradient; options.gradient = options.gradient || defaults.gradient;
options.gradient = String(JSON.parse(options.gradient)); options.gradient = JSON.parse(options.gradient);
progressObj.stop(true); progressObj.stop(true);
progressObj.overrideFlag = true; progressObj.overrideFlag = true;
@@ -63,8 +63,11 @@ module.exports = function Channel(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -8,7 +8,7 @@ module.exports = function ImportImageModuleUi(step, ui) {
// add a file input listener // add a file input listener
var dropZone = '\ var dropZone = '\
<div class="dropzone import-image-zone" id="' + dropzoneId + '">\ <div class="dropzone" style="padding: 30px;margin: 10px 20% 30px;border: 4px dashed #ccc;border-radius: 8px;text-align: center;color: #444;" id="' + dropzoneId + '">\
<p>\ <p>\
<i>Select or drag in an image to overlay.</i>\ <i>Select or drag in an image to overlay.</i>\
</p>\ </p>\

View File

@@ -1,4 +1,3 @@
const pixelManipulation = require('../_nomodule/PixelManipulation');
/* /*
* Invert the image * Invert the image
*/ */
@@ -18,11 +17,14 @@ function Invert(options, UI) {
return [255 - r, 255 - g, 255 - b, a]; return [255 - r, 255 - g, 255 - b, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return pixelManipulation(input, { return input.pixelManipulation({
output: output, output: output,
changePixel: changePixel, changePixel: changePixel,
format: input.format, format: input.format,
@@ -41,4 +43,10 @@ function Invert(options, UI) {
UI: UI UI: UI
}; };
} }
module.exports = Invert; var info = {
'name': 'invert',
'description': 'Inverts the image.',
'inputs': {
}
};
module.exports = [Invert, info];

View File

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

View File

@@ -49,7 +49,7 @@ module.exports = function MinifyImage(options, UI) {
reader.readAsDataURL(result); reader.readAsDataURL(result);
reader.onloadend = function () { reader.onloadend = function () {
base64data = reader.result; base64data = reader.result;
output(null, base64data, input.format, false); output(base64data, input.format);
if (callback) callback(); if (callback) callback();
return; return;
}; };
@@ -76,14 +76,19 @@ module.exports = function MinifyImage(options, UI) {
}); });
var destPath = __dirname + '/results/test.' + input.format; var destPath = __dirname + '/results/test.' + input.format;
var data = base64Img.base64Sync(destPath); var data = base64Img.base64Sync(destPath);
output(null, data, input.format, false); output(data, input.format);
if (callback) callback(); if (callback) callback();
})().catch(e => console.log(e)); })();
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = {
src: datauri,
format: mimetype
};
} }
} }
return { return {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

View File

@@ -25,8 +25,11 @@ module.exports = function Ndvi(options, UI) {
return [x, x, x, a]; return [x, x, x, a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
function modifiedCallback() { function modifiedCallback() {

View File

@@ -1,24 +1,6 @@
{ {
"name": "ndvi-colormap", "name": "ndvi-colormap",
"description": "Sequentially Applies NDVI and Colormap steps", "description": "Sequentially Applies NDVI and Colormap steps",
"inputs": { "inputs": {},
"filter": {
"type": "select",
"desc": "Filter color",
"default": "red",
"values": ["red", "blue"]
},
"colormap": {
"type": "select",
"desc": "Name of the Colormap",
"default": "default",
"values": [
"default",
"greyscale",
"stretched",
"fastie"
]
}
},
"docs-link": "https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#ndvi-colormap-module" "docs-link": "https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#ndvi-colormap-module"
} }

View File

@@ -15,8 +15,10 @@ module.exports = function NoiseReduction(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; // This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -25,7 +27,6 @@ module.exports = function NoiseReduction(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -4,17 +4,12 @@ module.exports = function Dynamic(options, UI, util) {
options.x = options.x || defaults.x; options.x = options.x || defaults.x;
options.y = options.y || defaults.y; options.y = options.y || defaults.y;
if(options.step.inBrowser && !options.noUI && sequencer.getSteps().length < 2)
options.offset = -1;
if (options.step.inBrowser && !options.noUI) var ui = require('./Ui.js')(options.step, UI);
var output; var output;
// This function is called on every draw. // This function is called on every draw.
function draw(input, callback, progressObj) { function draw(input, callback, progressObj) {
options.offset = parseInt(options.offset || defaults.offset); options.offset = parseInt(options.offset) || -2;
progressObj.stop(true); progressObj.stop(true);
progressObj.overrideFlag = true; progressObj.overrideFlag = true;
@@ -23,6 +18,16 @@ module.exports = function Dynamic(options, UI, util) {
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates'); 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 // save the pixels of the base image
var baseStepImage = this.getStep(options.offset).image; var baseStepImage = this.getStep(options.offset).image;
var baseStepOutput = this.getOutput(options.offset); var baseStepOutput = this.getOutput(options.offset);
@@ -30,19 +35,6 @@ module.exports = function Dynamic(options, UI, util) {
var getPixels = require('get-pixels'); var getPixels = require('get-pixels');
getPixels(input.src, function(err, 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; options.secondImagePixels = pixels;
function changePixel(r1, g1, b1, a1, x, y) { function changePixel(r1, g1, b1, a1, x, y) {
@@ -63,15 +55,11 @@ module.exports = function Dynamic(options, UI, util) {
return [r1, g1, b1, a1]; return [r1, g1, b1, a1];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
} // This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
function modifiedCallback() {
if (options.step.inBrowser && !options.noUI) {
ui.setup();
}
callback();
} }
// run PixelManipulation on first Image pixels // run PixelManipulation on first Image pixels
@@ -82,7 +70,7 @@ module.exports = function Dynamic(options, UI, util) {
format: baseStepOutput.format, format: baseStepOutput.format,
image: baseStepImage, image: baseStepImage,
inBrowser: options.inBrowser, inBrowser: options.inBrowser,
callback: modifiedCallback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });
}); });

View File

@@ -1,19 +0,0 @@
module.exports = function OverlayModuleUi(step, ui) {
function setup() {
var steps = sequencer.getSteps();
steps.forEach(function (_step, index) {
if(_step.options && step.options.number === _step.options.number) {
if(index === 1){
step.ui.querySelector('input[type=range]').value = -1;
step.ui.querySelector('input[type=range]').min = -1;
}else
step.ui.querySelector('input[type=range]').min = -index;
}
});
}
return {
setup: setup
};
};

View File

@@ -15,10 +15,7 @@
"offset": { "offset": {
"type": "integer", "type": "integer",
"desc": "offset to the output of the step on which the output of the last step is overlayed", "desc": "offset to the output of the step on which the output of the last step is overlayed",
"default": -2, "default": -2
"min": -2,
"max": -1,
"step": 1
} }
}, },
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#overlay-module" "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#overlay-module"

View File

@@ -16,8 +16,9 @@ module.exports = function PaintBucket(options, UI) {
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; // This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -24,7 +24,7 @@ module.exports = exports = function(pixels, options) {
minFactor = (1 - tolerance / 100); minFactor = (1 - tolerance / 100);
fillColor = fillColor.substring(fillColor.indexOf('(') + 1, fillColor.length - 1); // extract only the values from rgba(_,_,_,_) fillColor = fillColor.substring(fillColor.indexOf('(') + 1, fillColor.length - 1); // extract only the values from rgba(_,_,_,_)
fillColor = fillColor.split(','); fillColor = fillColor.split(',');
fillColor[3] = fillColor[3] * 255;
function isSimilar(currx, curry) { function isSimilar(currx, curry) {
return (pixels.get(currx, curry, 0) >= r * minFactor && pixels.get(currx, curry, 0) <= r * maxFactor && return (pixels.get(currx, curry, 0) >= r * minFactor && pixels.get(currx, curry, 0) <= r * maxFactor &&
pixels.get(currx, curry, 1) >= g * minFactor && pixels.get(currx, curry, 1) <= g * maxFactor && pixels.get(currx, curry, 1) >= g * minFactor && pixels.get(currx, curry, 1) <= g * maxFactor &&

View File

@@ -18,8 +18,11 @@ module.exports = function ReplaceColor(options, UI) {
return pixels; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
@@ -29,7 +32,6 @@ module.exports = function ReplaceColor(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

View File

@@ -1,14 +1,13 @@
module.exports = exports = function(pixels, options){ module.exports = exports = function(pixels, options){
const pixelSetter = require('../../util/pixelSetter.js'); const pixelSetter = require('../../util/pixelSetter.js');
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
var color = options.color || defaults.color; var color = options.color || 'rgb(228,86,81)';
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(_,_,_,_)
var replaceColor = options.replaceColor || defaults.replaceColor; var replaceColor = options.replaceColor || 'rgb(0,0,255)';
replaceColor = replaceColor.substring(replaceColor.indexOf('(') + 1, replaceColor.length - 1); // extract only the values from rgba(_,_,_,_) replaceColor = replaceColor.substring(replaceColor.indexOf('(') + 1, replaceColor.length - 1); // extract only the values from rgba(_,_,_,_)
var replaceMethod = options.replaceMethod || defaults.replaceMethod; var replaceMethod = options.replaceMethod || 'greyscale';
color = color.split(','); color = color.split(',');
replaceColor = replaceColor.split(','); replaceColor = replaceColor.split(',');
@@ -17,7 +16,7 @@ module.exports = exports = function(pixels, options){
cg = color[1], cg = color[1],
cb = color[2]; cb = color[2];
var tolerance = options.tolerance || defaults.tolerance; var tolerance = options.tolerance || 50;
var maxFactor = (1 + tolerance / 100); var maxFactor = (1 + tolerance / 100);
var minFactor = (1 - tolerance / 100); var minFactor = (1 - tolerance / 100);

View File

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

View File

@@ -3,63 +3,41 @@
*/ */
module.exports = function Rotate(options, UI) { module.exports = function Rotate(options, UI) {
let output; var output;
function draw(input, callback, progressObj) { function draw(input, callback, progressObj) {
const defaults = require('./../../util/getDefaults.js')(require('./info.json')); var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.rotate = options.rotate || defaults.rotate; options.rotate = options.rotate || defaults.rotate;
progressObj.stop(true); progressObj.stop(true);
progressObj.overrideFlag = true; progressObj.overrideFlag = true;
const step = this; var step = this;
var imagejs = require('imagejs');
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {
return [r, g, b, a]; return [r, g, b, a];
} }
function extraManipulation(pixels) { function extraManipulation(pixels) {
const rotate_value = (options.rotate) % 360; var rotate_value = (options.rotate) % 360;
radians = (Math.PI) * rotate_value / 180, var radians = (Math.PI) * rotate_value / 180;
width = pixels.shape[0], var width = pixels.shape[0];
height = pixels.shape[1], var height = pixels.shape[1];
cos = Math.cos(radians), var cos = Math.cos(radians);
sin = Math.sin(radians); var sin = Math.sin(radians);
// Final dimensions after rotation //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]);
const finalPixels = require('ndarray')( pixels = require('./Rotate')(pixels, pixels2, options, rotate_value, width, height, cos, sin);
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; return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; // This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

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

@@ -31,8 +31,11 @@ module.exports = function Saturation(options, UI) {
return [Math.round(r), Math.round(g), Math.round(b), a]; return [Math.round(r), Math.round(g), Math.round(b), a];
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -7,21 +7,19 @@ module.exports = function TextOverlay(options, UI) {
var step = this; var step = this;
function extraManipulation(pixels, setRenderState, generateOutput) { var priorStep = this.getStep(-1); // get the previous step to add text onto it.
//if (options.step.inBrowser)
const getDataUri = require('../../util/getDataUri');
getDataUri(pixels, input.format).then(dataUri => {
setRenderState(false);
pixels = require('./TextOverlay')(pixels, options, dataUri, () => {
setRenderState(true);
generateOutput();
});
}); function extraManipulation(pixels) {
//if (options.step.inBrowser)
pixels = require('./TextOverlay')(pixels, options, priorStep);
return pixels;
} }
function output(image, datauri, mimetype, wasmSuccess) { function output(image, datauri, mimetype) {
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
// This output is accesible by Image Sequencer
step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {

View File

@@ -1,6 +1,7 @@
const getPixels = require('get-pixels'), module.exports = exports = function(pixels, options, priorstep){
pixelSetter = require('../../util/pixelSetter.js');
module.exports = exports = function(pixels, options, url1, cb){ var $ = require('jquery'); // to make text-overlay work for node.js
var defaults = require('./../../util/getDefaults.js')(require('./info.json')); var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
options.text = options.text || defaults.text; options.text = options.text || defaults.text;
@@ -10,46 +11,20 @@ module.exports = exports = function(pixels, options, url1, cb){
options.color = options.color || defaults.color; options.color = options.color || defaults.color;
options.size = options.size || defaults.size; 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'); var canvas = document.createElement('canvas');
canvas.width = pixels.shape[0]; //img.width(); canvas.width = pixels.shape[0]; //img.width();
canvas.height = pixels.shape[1]; //img.height(); canvas.height = pixels.shape[1]; //img.height();
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
var image = new Image; ctx.drawImage(img[0], 0, 0);
image.src = url1;
image.onload = function(){
ctx.drawImage(image, 0, 0);
ctx.fillStyle = options.color; ctx.fillStyle = options.color;
ctx.font = options.size + 'px ' + options.font; ctx.font = options.size + 'px ' + options.font;
ctx.fillText(options.text, options.x, options.y); ctx.fillText(options.text, options.x, options.y);
getPixels(canvas.toDataURL(), function (err, qrPixels) { var myImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (err) { pixels.data = myImageData.data;
console.log('get-pixels error: ', err); return pixels;
}
for (let x = 0; x < pixels.shape[0]; x++) {
for (let y = 0; y < pixels.shape[1]; y++) {
pixelSetter(
x,
y,
[
qrPixels.get(x, y, 0),
qrPixels.get(x, y, 1),
qrPixels.get(x, y, 2),
qrPixels.get(x, y, 3)
],
pixels
);
}
}
if (cb) cb();
});
};
}; };

View File

@@ -1,7 +1,6 @@
{ {
"name": "text-overlay", "name": "text-overlay",
"description": "Overlay text on image.", "description": "Overlay text on image.",
"requires": ["webgl", "browser"],
"inputs": { "inputs": {
"text": { "text": {
"type": "string", "type": "string",

View File

@@ -22,11 +22,10 @@ module.exports = function ImageThreshold(options, UI) {
pixels = require('./Threshold')(pixels, options, hist); pixels = require('./Threshold')(pixels, options, hist);
return pixels; return pixels;
} }
function output(image, datauri, mimetype) {
function output(image, datauri, mimetype, wasmSuccess) { // This output is accessible by Image Sequencer
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm }; step.output = { src: datauri, format: mimetype };
} }
return require('../_nomodule/PixelManipulation.js')(input, { return require('../_nomodule/PixelManipulation.js')(input, {
output: output, output: output,
ui: options.step.ui, ui: options.step.ui,
@@ -34,7 +33,6 @@ module.exports = function ImageThreshold(options, UI) {
extraManipulation: extraManipulation, extraManipulation: extraManipulation,
format: input.format, format: input.format,
image: options.image, image: options.image,
inBrowser: options.inBrowser,
callback: callback, callback: callback,
useWasm:options.useWasm useWasm:options.useWasm
}); });

Some files were not shown because too many files have changed in this diff Show More