mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-07 17:00:02 +01:00
Compare commits
1 Commits
gitpod-mai
...
origin/mco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
856880cb26 |
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,15 +2,15 @@ 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!
|
||||
|
||||
* [ ] 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
|
||||
* [ ] PR is descriptively titled
|
||||
* [ ] ask `@publiclab/is-reviewers` for help, in a comment below
|
||||
* [ ] Insert-step functionality is working correct as expected.
|
||||
|
||||
> We're happy to help you get this ready -- don't be afraid to ask for help, and **don't be discouraged** if your tests fail at first!
|
||||
|
||||
If tests do fail, click on the red `X` to learn why by reading the logs.
|
||||
|
||||
Please be sure you've reviewed our contribution guidelines at https://publiclab.org/contributing-to-public-lab-software
|
||||
Please make sure to get at least two reviews before asking for merging the PR as that would make the PR more reliable on our part
|
||||
|
||||
Thanks!
|
||||
|
||||
17
.gitpod.yml
17
.gitpod.yml
@@ -1,17 +0,0 @@
|
||||
tasks:
|
||||
- init: npm run setup
|
||||
command: npm start
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: open-preview
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
main: true
|
||||
branches: true
|
||||
pullRequests: true
|
||||
pullRequestsFromForks: true
|
||||
addCheck: true
|
||||
addComment: true
|
||||
addBadge: false
|
||||
addLabel: false
|
||||
@@ -12,12 +12,7 @@ before_script:
|
||||
- ./cc-test-reporter before-build
|
||||
script:
|
||||
- npm test
|
||||
- npm run benchmark
|
||||
- npm run gif-test
|
||||
- grunt tests
|
||||
- npm run core-tests
|
||||
- npm run test-ui
|
||||
- npm run test-ui-2
|
||||
- grunt build
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
@@ -1,11 +1,114 @@
|
||||
This document was copied from its home at https://publiclab.org/conduct on October 12, 2017. See that page if you would like to submit your concerns in a safe, completely anonymous way, and to learn more about this document.
|
||||
|
||||
****
|
||||
|
||||
# Public Lab Code of Conduct
|
||||
|
||||
_Public Lab, 55 Cromwell Street, 1C, Providence, RI 02907_
|
||||
_Public Lab, PO Box 426113, Cambridge, MA 02142_
|
||||
|
||||
We are coming together with an intent to care for ourselves and one another as we produce knowledge in pursuit of environmental justice. For this to work for everybody, individual decisions will not be allowed to run counter to the welfare of other people. We—visitors, community members, community moderators, staff, organizers, sponsors, and all others—hold ourselves accountable to the same values regardless of position or experience. This community aspires to be a respectful place both during online and in-person interactions so that all people are able to fully participate with their dignity intact. This document is a piece of the culture we're creating.
|
||||
We are coming together with an intent to care for ourselves and one another. We want to nurture a compassionate democratic culture where responsibility is shared. We -- visitors, community members, community moderators, staff, organizers, sponsors, and all others -- hold ourselves accountable to the same values regardless of position or experience. For this to work for everybody, individual decisions will not be allowed to run counter to the welfare of other people. This community aspires to be a respectful place both during online and in-person interactions so that all people are able to fully participate with their dignity intact. This document is a piece of the culture we're creating.
|
||||
|
||||
This code of conduct applies to all spaces managed by the Public Lab community and non-profit, both online and in person. It provides a clear set of practical guidelines for events led by organizers and community members, multi-day events such as Barnraisings, and online venues such as the website, comment threads on software platforms, chatrooms, our mailing lists, the issue tracker, and any other forums created by Public Lab which the community uses for communication. For interactions with additional groups, see our Partnership Guidelines at https://publiclab.org/partners.
|
||||
|
||||
To read the full Code of Conduct and learn how to contact the Conduct Committee or the Moderators group, see:
|
||||
This code of conduct applies to all spaces managed by the Public Lab community and non-profit, both online and in person. It was written by the Conduct Committee (formed in 2015 during Public Lab’s annual conference “The Barnraising”) and facilitated by staff to provide a clear set of practical guidelines for multi-day events such as Barnraisings, events led by organizers and community members, and online venues such as the website, comment threads on software platforms, chatrooms, our mailing lists, the issue tracker, and any other forums created by Public Lab which the community uses for communication.
|
||||
|
||||
https://publiclab.org/conduct
|
||||
|
||||
## We come from all kinds of backgrounds
|
||||
|
||||
Our community is best when we fully invite and include participants from a wide range of backgrounds. We specifically design spaces to be welcoming and accessible to newcomers and folks from underrepresented groups. Public Lab is dedicated to providing a harassment-free, safe, and inclusive experience for everyone, regardless of personal and professional background, gender, gender identity and expression, style of clothing, sexual orientation, dis-/ability, physical appearance, body size, race, class, age, or religion. Public Lab resists and rejects: racism, sexism, ableism, ageism, homophobia, transphobia, body shaming, religion shaming, “geekier-than-thou” shaming, education bias, the shaming of people nursing children, and the dismissal or bullying of children or adults.
|
||||
|
||||
## We do not tolerate harassment or shaming
|
||||
|
||||
While we operate under the assumption that all people involved with Public Lab subscribe to the basic understanding laid out above, we take these issues very seriously and think they should, in general, be taken seriously. Therefore, individuals who violate this Code both in and outside of Public Lab spaces may affect their ability to participate in Public Lab ranging from temporarily being placed into online moderation to, as a last resort, expulsion from the community. If you have any questions about our commitment to this framework and/or if you are unsure about aspects of it, email conduct@publiclab.org and we will do our best to provide clarification.
|
||||
|
||||
## How It Works
|
||||
|
||||
Sometimes things go wrong. When a situation is uncomfortable, hurtful, exclusionary, or upsetting, there is a problem that should be addressed. This code of conduct is an effort to maintain a safe space for everyone, and to talk about what might happen if that space is compromised. Please see additional guidelines below for community behavior on how we expect people to interact with one another.
|
||||
|
||||
### Two helpful groups
|
||||
|
||||
__Conduct Committee (ConductCom)__: If at any time you experience something that you are not comfortable with, you may contact the Conduct Committee. As established during the 2015 Annual Barnraising, the following individuals are on the Conduct Committee: Klie Kliebert, Carla Green, Nick Shapiro, and Shannon Dosemagen, the executive director of the Public Lab nonprofit.
|
||||
|
||||
|
||||
If you would like to have a confidential conversation, connect with ConductCom in person or email via [conduct@publiclab.org](mailto:conduct@publiclab.org). A minimum of two committee members will confer and respond as swiftly as possible. If you would prefer to speak privately with a representative of the nonprofit, please contact the executive director directly either in person or by email: [shannon@publiclab.org.](mailto:conduct@publiclab.org)
|
||||
|
||||
|
||||
To submit a report anonymously for review by ConductCom, go online via phone or computer to our anonymous “contact” app, located at [http://bit.ly/PLReport](http://bit.ly/PLReport). This contact app will be monitored daily at 8am CST during in-person events like Barnraisings and weekly at all other times. During multi-day in-person events hosted by the Public Lab non-profit, there will also be a physical suggestion box available. This box will be monitored throughout the event and can also be used to let us know if you need us to check on an anonymous online submission sooner.
|
||||
|
||||
|
||||
__Moderators Group:__ The moderators group is responsible for addressing immediate moderation issues that arise during online violations of the code over email lists and Public Lab community websites, as well as approving first-time posts and generally handling spam. Instructions on how to become a moderator, and, if you’ve been placed in moderation how to begin the process of getting out of moderation can be found at: https://publiclab.org/wiki/moderation.
|
||||
|
||||
****
|
||||
|
||||
## A Culture of Empathy
|
||||
|
||||
We begin interactions by acknowledging that we are part of a community with complementary goals. Different views are allowed to respectfully coexist in the same space. When something's happened and someone is uncomfortable, our first choice is to work through it. Endeavor to listen and appropriately adjust your behavior if someone approaches you privately with a request that you apologize or publicly requests that you stop an ongoing presentation. If someone questions your words, actions or motives, or "calls you out", hear their feedback and respond respectfully. It’s okay to not understand why something is hurtful or causes discomfort, as long as you approach it respectfully, with empathy. Repeating hurtful behavior after it has been addressed is disrespectful and is not allowed. Doing so will result in removal and subsequent banning from in-person events and being placed into moderation in online spaces.
|
||||
|
||||
### The first rule of engaging with others is consent
|
||||
|
||||
During in-person gatherings, consent is important to highlight because the negotiation of consent can be subtle, and it’s easy to miss each other’s non-verbal cues, resulting in miscommunication and/or offense. During online interactions, consent can be even harder to distinguish.
|
||||
|
||||
We make guesses or assessments of consent (willingness, welcome, invitation) all the time. Then we stay open to signs that the consent isn't there. Handshakes are a clear example of consent: someone offers a hand, and you take it if you want to shake it. A friendly smile might indicate consent to start a conversation. It might not. We learn that in the interaction. Sometimes we ask directly. We are open to making mistakes, and learning from them. The more we learn to be empathetic and see other people, the more we're able to talk about consent.
|
||||
|
||||
Before you engage with someone on any level, be sure you have their consent. If your indications aren't being heard, you can also ask for help from other folks, especially Conduct Committee members and staff of the non-profit: "They aren't taking the hint. Will you help?" Turning a blind eye to hurtful interactions can be as bad for our community as the exchange itself. If you witness something, it's your responsibility to say something. This is how we keep each other accountable, encourage empathy, and keep our community safe.
|
||||
|
||||
## Guidelines for in-person community behavior
|
||||
|
||||
|
||||
Do | Don’t
|
||||
----------- | -----------
|
||||
Respectfully share what method works best for you, while giving others space to think differently and contribute other ideas | Disparage entire groups/sets of people for their beliefs or methods
|
||||
Ask permission to take pictures of and post about others on social media (see Media Consent, above)| Upload photos, tag or mention others online without their consent
|
||||
Speak your own narrative, from your own unique culture | Caricature the cultural expressions of groups you are not a member of
|
||||
Model inclusionary expertise - if others in the group appear to be “lost”, slow down; stop and ask for input | Present information in a way / at a level that no one else in the room can understand, with no attempt to include others in the discussion
|
||||
Create events that are all-ages appropriate | Use language that excludes youth and their experiences as vital contributors
|
||||
Give everyone a chance to talk, only interrupting if absolutely necessary - for example, Code of Conduct violations | Repeatedly disrupt a discussion
|
||||
Stop, listen and ask for clarification if someone perceives your behavior or presentation as violating the Code of Conduct | Ignore others’ request to stop potentially harmful behavior, even if it was an accident
|
||||
Cultivate a sense of humor based on other subjects, such as word play (especially puns!) | Joke using words related to actual or insulting descriptions of people
|
||||
Use words that accurately describe the situation - For example, “The wind was ridiculously strong!” instead of “The wind was crazy!” | Use disability and mental/emotional health terminology to describe a situation metaphorically, especially if the phrasing is meant as an insult
|
||||
Only discuss someone else’s lifestyle practices if they invite you to a conversation on the topic | Make unwelcomed comments regarding a person’s lifestyle practices, including those related to food, health, parenting, relationships, and employment
|
||||
Ask someone before you hug them; keep your hands/body to yourself, even when joking, unless the other person has given verbal consent | Initiate physical contact or simulate physical contact without consent
|
||||
Disengage and find another activity if someone did not invite you and is not engaging with you | Violate personal space by continuing your physical presence into private spaces without consent
|
||||
Exercise the right to talk about your own identity if you want to, or not if you don’t want to | Deliberately “out” any aspect of a person’s identity without their consent
|
||||
Use the pronouns people have specified for themselves | Purposely misgender someone (ie, refusing to use their correct gender pronouns) after they have told you their correct pronouns
|
||||
|
||||
|
||||
## Additional guidelines for online community behavior
|
||||
|
||||
|
||||
Online modes of interaction involve large numbers of people without the helpful presence of gestural, expression, and tonal cues regarding consent. Because of this, respectful and self-aware online conduct is both especially important and difficult. Our community has evolved specific guidelines for online interactions.
|
||||
|
||||
_If someone violates these guidelines, someone from the Moderators group will place them into moderation by changing that person’s posting permission on the relevant list, on the website, or both._
|
||||
|
||||
Our triple notification standard for moderation means a point person from the Moderators group will:
|
||||
|
||||
1. e-mail the person directly with a brief explanation of what was violated,
|
||||
2. send a summary email to the rest of the moderators group,
|
||||
3. if it happened on a public list (vs a website), notify the list that one of our members has been placed into moderation with a brief explanation of what is not tolerated.
|
||||
|
||||
|
||||
If you wish to begin the process of getting out of moderation, respond to the email sent to you from [moderators@publiclab.org](mailto:moderators@publiclab.org). The Moderators group has the option to involve ConductCom.
|
||||
|
||||
|
||||
Do | Don’t
|
||||
-------|--------
|
||||
Stay on topic to make long threads easier to follow |Send spurious one-line responses that effectively "spam" hundreds of people and lower the overall content quality of a conversation. (Exception: expressions of appreciation and encouragement!)
|
||||
Start a new thread to help others follow along. Important if your response starts to significantly diverge from the original topic | Respond with off-topic information making it hard for the large group of readers to follow along
|
||||
Write short and literal subject lines to help the readers of the list manage the volume of communication | Humor and euphemisms in subject lines are easily misunderstood, although enthusiasm is welcome!
|
||||
Mind your tone. We are not having this conversation in person, so it is all the more important to maintain a tone of respect | Write in aggressive tone, disrespectful tone, mocking tone, off-color tone. Note: writing in all caps is regarded as shouting
|
||||
|
||||
## Media Consent
|
||||
|
||||
* ALWAYS check with parents about posting anything with minors.
|
||||
* Never post the names of minors in conjunction with their photo.
|
||||
* During multi-day events like Barnraisings most people will have signed media releases. Those who haven’t will be responsible for placing stickers on their nametags, and/or raising their hands in the moment to alert photographers to move them out of frame.
|
||||
* For events where people have not signed blanket media release forms, the photographer is responsible for letting the room know that you are taking photos that will be posted online. Pay special attention to the presence of minors and their parent's wishes.
|
||||
|
||||
|
||||
## Addendum for all staff
|
||||
|
||||
Staff are bound by their Employment Handbook, you must reference it. Additionally:
|
||||
|
||||
* Direct problems that come up among community members to the Conduct Committee.
|
||||
* When organizing events, circulate access information regarding wheelchair-accessible ADA bathrooms, non-gendered bathrooms, the presence of stairs or curb ramps in the parking lot, et cetera.
|
||||
* During events that you are attending in person, solve accessibility issues by making sure attendees know where bathrooms are located and can access them by wheelchair without being obstructed by things like chairs, kites, contraptions, or cords.
|
||||
* Watch for people feeling left out and include them.
|
||||
|
||||
@@ -5,7 +5,7 @@ Happily accepting pull requests; to edit the core library, modify files in `./sr
|
||||
|
||||
On ARM based devices, the `gl` module may require some libraries to be re-installed:
|
||||
|
||||
`sudo apt-get install -y build-essential xserver-xorg-dev libxext-dev libxi-dev libglu1-mesa-dev libglew-dev pkg-config` -- see https://github.com/stackgl/headless-gl#ubuntudebian for more.
|
||||
`sudo apt-get install -y build-essential libxi-dev libglu1-mesa-dev libglew-dev pkg-config` -- see https://github.com/stackgl/headless-gl#ubuntudebian for more.
|
||||
|
||||
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
|
||||
|
||||
|
||||
29
Gruntfile.js
29
Gruntfile.js
@@ -2,7 +2,6 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify-es');
|
||||
grunt.loadNpmTasks('grunt-browser-sync');
|
||||
grunt.loadNpmTasks('grunt-text-replace');
|
||||
|
||||
require('matchdep')
|
||||
.filterDev('grunt-*')
|
||||
@@ -22,11 +21,6 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
browserify: {
|
||||
options: {
|
||||
alias: {
|
||||
'gpu.js': './node_modules/gpu.js/src/index.js'
|
||||
}
|
||||
},
|
||||
core: {
|
||||
src: ['src/ImageSequencer.js'],
|
||||
dest: 'dist/image-sequencer.js'
|
||||
@@ -42,21 +36,6 @@ module.exports = function(grunt) {
|
||||
produi: {
|
||||
src: ['examples/demo.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 %>';"
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -90,10 +69,8 @@ module.exports = function(grunt) {
|
||||
|
||||
/* Default (development): Watch files and build on change. */
|
||||
grunt.registerTask('default', ['watch']);
|
||||
grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'replace:version', 'uglify:core', 'uglify:ui']);
|
||||
grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'replace:version', 'browserSync', 'watch']);
|
||||
grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'uglify:core', 'uglify:ui']);
|
||||
grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'browserSync', 'watch']);
|
||||
grunt.registerTask('compile', ['browserify:core', 'browserify:ui']);
|
||||
grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'replace:version', 'uglify:prodcore', 'uglify:produi']);
|
||||
|
||||
grunt.registerTask('tests', ['browserify:tests']);
|
||||
grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'uglify:prodcore', 'uglify:produi']);
|
||||
};
|
||||
|
||||
27
README.md
27
README.md
@@ -1,9 +1,8 @@
|
||||
Image Sequencer
|
||||
====
|
||||
|
||||
[](https://publiclab.org/conduct)
|
||||
|
||||
[](https://travis-ci.org/publiclab/image-sequencer) [](https://codeclimate.com/github/publiclab/image-sequencer/maintainability) [](https://codecov.io/gh/publiclab/image-sequencer)
|
||||
[](https://gitpod.io/from-referrer/)
|
||||
|
||||
- **Latest Stable Demo**: https://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
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
[](https://gitpod.io/from-referrer/)
|
||||
|
||||
### 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)!
|
||||
@@ -249,7 +242,7 @@ If only one module is to be added, `modules` is simply the name of the module.
|
||||
If multiple images are to be added, `modules` is an array, which holds the names of modules
|
||||
to be added, in that particular order.
|
||||
|
||||
optional_options is just an optional parameter, in object form, which you might
|
||||
optional_otions is just an optional parameter, in object form, which you might
|
||||
want to provide to the modules.
|
||||
|
||||
A variety of syntaxes are supported by Image Sequencer to add multiple steps and configurations quickly for module chaining. The project supports the string syntax, designed to be compact and URL friendly, and JSON, for handling more complex sequences. This can be achieved by passing strings to `sequencer.addStep()`:
|
||||
@@ -605,18 +598,4 @@ let sequencer = ImageSequencer() // also for wasm mode i.e. default mode
|
||||
|
||||
let sequencer = ImageSequencer({useWasm:false}) //for non-wasm mode
|
||||
|
||||
```
|
||||
|
||||
## Experimental GIF processing support
|
||||
|
||||
ImageSequencer currently can process GIFs but only for most of the modules. Every frame of the GIF is manipulated sequentially (parallel processing would be preferable in the future).
|
||||
The final frames are then converted back to a GIF but in the process, the time duration of each frame is lost and defaults to `0.1s`.
|
||||
|
||||
Modules that do not work:
|
||||
1. ColorBar (Will get fixed upon fixing overlay as this is a meta module which uses overlay)
|
||||
2. FisheyeGL
|
||||
4. Overlay
|
||||
5. Text Overlay (Almost fixed)
|
||||
6. Blend
|
||||
7. Histogram
|
||||
8. WebGL Distort
|
||||
```
|
||||
165
docs/MODULES.md
165
docs/MODULES.md
@@ -8,44 +8,39 @@ List of Module Documentations
|
||||
3. [Blend](#blend-module)
|
||||
4. [Blur](#blur-module)
|
||||
5. [Brightness](#brightness-module)
|
||||
6. [Canvas-Resize](#canvas-resize-module)
|
||||
7. [Channel](#channel-module)
|
||||
8. [Colorbar](#colorbar-module)
|
||||
9. [Colormap](#colormap-module)
|
||||
10. [ColorTemperature](#color-temperature)
|
||||
11. [Contrast](#contrast-module)
|
||||
12. [Convolution](#convolution-module)
|
||||
13. [Crop](#crop-module)
|
||||
14. [DecodeQr](#decodeQr-module)
|
||||
15. [Dither](#dither-module)
|
||||
16. [DrawRectangle](#draw-rectangle-module)
|
||||
17. [Dynamic](#dynamic-module)
|
||||
18. [Edge-Detect](#edge-detect-module)
|
||||
19. [Exposure](#exposure-module)
|
||||
20. [FisheyeGl](#fisheyeGl-module)
|
||||
21. [FlipImage](#flipimage-module)
|
||||
22. [Gamma-Correction](#gamma-correction-module)
|
||||
23. [Gradient](#gradient-module)
|
||||
24. [Grid-Overlay](#grid-overlay)
|
||||
25. [Histogram](#histogram-module)
|
||||
26. [Import-image](#import-image-module)
|
||||
27. [Invert](#invert-module)
|
||||
28. [MinifyImage](#minify-image)
|
||||
29. [Ndvi](#ndvi-module)
|
||||
30. [Ndvi-Colormap](#ndvi-colormap-module)
|
||||
31. [NoiseReduction](#noise-reduction)
|
||||
32. [Overlay](#overlay-module)
|
||||
33. [PaintBucket](#paint-bucket-module)
|
||||
34. [ReplaceColor](#replacecolor-module)
|
||||
35. [Resize](#resize-module)
|
||||
36. [Rotate](#rotate-module)
|
||||
37. [Saturation](#saturation-module)
|
||||
38. [Segmented-Colormap](#segmented-colormap-module)
|
||||
39. [Text-Overlay](#text-overlay)
|
||||
40. [Threshold](#threshold)
|
||||
41. [Tint](#tint)
|
||||
42. [WebGL-Distort](#webgl-distort-module)
|
||||
43. [White-Balance](#white-balance-module)
|
||||
6. [Channel](#channel-module)
|
||||
7. [Colorbar](#colorbar-module)
|
||||
8. [Colormap](#colormap-module)
|
||||
9. [ColorTemperature](#color-temperature)
|
||||
10. [Contrast](#contrast-module)
|
||||
11. [Convolution](#convolution-module)
|
||||
12. [Crop](#crop-module)
|
||||
13. [DecodeQr](#decodeQr-module)
|
||||
14. [Dither](#dither-module)
|
||||
15. [DrawRectangle](#draw-rectangle-module)
|
||||
16. [Dynamic](#dynamic-module)
|
||||
17. [Edge-Detect](#edge-detect-module)
|
||||
18. [FisheyeGl](#fisheyeGl-module)
|
||||
19. [FlipImage](#flipimage-module)
|
||||
20. [Gamma-Correction](#gamma-correction-module)
|
||||
21. [Gradient](#gradient-module)
|
||||
22. [Grid-Overlay](#grid-overlay)
|
||||
23. [Histogram](#histogram-module)
|
||||
24. [Import-image](#import-image-module)
|
||||
25. [Invert](#invert-module)
|
||||
26. [MinifyImage](#minify-image)
|
||||
27. [Ndvi](#ndvi-module)
|
||||
28. [Ndvi-Colormap](#ndvi-colormap-module)
|
||||
29. [Overlay](#overlay-module)
|
||||
30. [PaintBucket](#paint-bucket-module)
|
||||
31. [ReplaceColor](#replacecolor-module)
|
||||
32. [Resize](#resize-module)
|
||||
33. [Rotate](#rotate-module)
|
||||
34. [Saturation](#saturation-module)
|
||||
35. [Segmented-Colormap](#segmented-colormap-module)
|
||||
36. [Text-Overlay](#text-overlay)
|
||||
37. [Threshold](#threshold)
|
||||
38. [Tint](#tint)
|
||||
|
||||
|
||||
## add-qr-module
|
||||
@@ -78,8 +73,7 @@ This module is used for averaging all the pixels of the image.
|
||||
|
||||
## 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
|
||||
|
||||
```js
|
||||
@@ -89,12 +83,8 @@ This module is used for blending two images. For More info read: _[wiki](https:
|
||||
```
|
||||
|
||||
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)
|
||||
* 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 ] })
|
||||
|
||||
[More info for different blend modes can be found here](http://docs.gimp.org/en/gimp-concepts-layer-modes.html)
|
||||
|
||||
* 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)
|
||||
* func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] })
|
||||
|
||||
## Blob Analysis
|
||||
|
||||
@@ -140,24 +130,6 @@ where `options` is an object with the following property:
|
||||
* 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
|
||||
|
||||
This module is used for forming a grayscale image by applying one of the three primary colors.
|
||||
@@ -354,21 +326,6 @@ where `options` is an object with the following properties:
|
||||
* 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
|
||||
|
||||
This module is used for correcting Fisheye or Lens Distortion
|
||||
@@ -531,21 +488,6 @@ This module is used for demonstrating ndvi and colormap properties consecutively
|
||||
```
|
||||
|
||||
|
||||
## Noise-Reduction
|
||||
|
||||
Noise in an image are atypical pixels that are not representing the color or the exposure of the scene correctly. This Noise Reduction module reduces the noise in the image by using either median filtering or mean filtering techniques to change the RGB value of the pixels to create a smoother and fuller image.
|
||||
|
||||
#### Usage
|
||||
```js
|
||||
sequencer.loadImage('PATH')
|
||||
.addSteps('noise-reduction',options)
|
||||
.run()
|
||||
```
|
||||
where `options` is an object with the property `method`. `options.method` can be:
|
||||
* Median Filtering: Set the RGB value of the pixel to the median RGB pixel value of all adjacent pixels (maximum 8 adjacent pixels and itself)
|
||||
* Mean Filtering: Set the RGB value of the pixel to the mean RGB pixel value of all adjacent pixels (maximum 8 adjacent pixels and itself)
|
||||
|
||||
|
||||
## overlay-module
|
||||
|
||||
This module is used for overlaying an Image over another .
|
||||
@@ -709,38 +651,3 @@ It adds color tint to an image
|
||||
where `options` is an object with the following property:
|
||||
* color : RGB values seperated by a space (default "0 0 255")
|
||||
* 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)
|
||||
|
||||
@@ -33,7 +33,7 @@ body > .container-fluid {
|
||||
|
||||
.center-align {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
justify-content: center;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ body > .container-fluid {
|
||||
.panel {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
min-width:400px;
|
||||
}
|
||||
|
||||
.mouse {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
@@ -66,27 +61,16 @@ body > .container-fluid {
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: #444;
|
||||
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 {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.dropzone input {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -119,10 +103,7 @@ body > .container-fluid {
|
||||
}
|
||||
|
||||
#add-step-btn{
|
||||
width: 100%
|
||||
}
|
||||
.selectize-input {
|
||||
width: 100%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#addStep .labels {
|
||||
@@ -159,6 +140,7 @@ body > .container-fluid {
|
||||
#dwnld {
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#gif_element {
|
||||
@@ -262,14 +244,13 @@ a.name-header{
|
||||
}
|
||||
|
||||
.step-column{
|
||||
display:flex;
|
||||
display:flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.trash-container button.btn-xs {
|
||||
position: relative;
|
||||
bottom: 7px;
|
||||
margin-top: -5px !important;
|
||||
}
|
||||
|
||||
.toggleIcon {
|
||||
@@ -286,84 +267,12 @@ a.name-header{
|
||||
width:100%;
|
||||
}
|
||||
.save-button{
|
||||
margin-top:20px;
|
||||
margin-top:20px;
|
||||
margin-bottom:0px;
|
||||
align:center;
|
||||
align:center;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.general-tooltip:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.general-tooltip:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.general-tooltip:focus-within{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.general-tooltip{
|
||||
position: relative;
|
||||
bottom: 7px;
|
||||
font-size: 16px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
259
examples/demo.js
259
examples/demo.js
@@ -3,48 +3,12 @@ var defaultHtmlSequencerUi = require('./lib/defaultHtmlSequencerUi.js'),
|
||||
intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'),
|
||||
DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'),
|
||||
urlHash = require('./lib/urlHash.js'),
|
||||
insertPreview = require('./lib/insertPreview.js'),
|
||||
versionManagement = require('./lib/versionManagement.js'),
|
||||
isGIF = require('../src/util/isGif');
|
||||
insertPreview = require('./lib/insertPreview.js');
|
||||
|
||||
window.onload = function() {
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
window.onload = function () {
|
||||
sequencer = ImageSequencer(); // Set the global sequencer variable
|
||||
|
||||
options = {
|
||||
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' };
|
||||
function refreshOptions() {
|
||||
// Load information of all modules (Name, Inputs, Outputs)
|
||||
var modulesInfo = sequencer.modulesInfo();
|
||||
|
||||
@@ -60,16 +24,15 @@ window.onload = function () {
|
||||
}
|
||||
// Null 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);
|
||||
|
||||
/**
|
||||
* @description Method to toggle the scroll-up arrow.
|
||||
*/
|
||||
function scrollFunction(A, B) {
|
||||
function scrollFunction() {
|
||||
var shouldDisplay = $('body').scrollTop() > 20 || $(':root').scrollTop() > 20;
|
||||
|
||||
$('#move-up').css({
|
||||
@@ -77,9 +40,7 @@ window.onload = function () {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Method to scroll to the top of the page.
|
||||
*/
|
||||
|
||||
function topFunction() {
|
||||
$('body').animate({scrollTop: 0});
|
||||
$(':root').animate({scrollTop: 0});
|
||||
@@ -94,86 +55,59 @@ window.onload = function () {
|
||||
// UI for the overall demo:
|
||||
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')) {
|
||||
sequencer.loadImage(urlHash.getUrlHashParameter('src'), ui.onLoad);
|
||||
} else {
|
||||
sequencer.loadImage('images/tulips.png', ui.onLoad);
|
||||
}
|
||||
|
||||
var resetSequence = function () {
|
||||
var resetSequence = function(){
|
||||
var r = confirm('Do you want to reset the sequence?');
|
||||
if (r)
|
||||
{
|
||||
window.location.hash = '';
|
||||
location.reload();
|
||||
}
|
||||
window.location = '/';
|
||||
};
|
||||
|
||||
$('#addStep select').on('change', ui.selectNewStepUi);
|
||||
$('#addStep #add-step-btn').on('click', ui.addStepUi);
|
||||
$('#resetButton').on('click', resetSequence);
|
||||
|
||||
// Module Selector quick buttons click handler.
|
||||
$('.radio-group .radio').on('click', function () {
|
||||
//Module button radio selection
|
||||
$('.radio-group .radio').on('click', function() {
|
||||
$(this).parent().find('.radio').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
newStep = $(this).attr('data-value');
|
||||
|
||||
//$("#addStep option[value=" + newStep + "]").attr('selected', 'selected');
|
||||
$('#addStep select').val(newStep);
|
||||
ui.selectNewStepUi(newStep);
|
||||
ui.addStepUi(newStep);
|
||||
$(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();
|
||||
setTimeout(function () {
|
||||
setTimeout(function() {
|
||||
$('.savesequencemsg').fadeOut();
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
$('body').on('click', 'button.remove', ui.removeStepUi);
|
||||
function saveSequence() { // 1. save seq
|
||||
$('#save-seq').click(() => {
|
||||
var result = window.prompt('Please give a name to your sequence... (Saved sequence will only be available in this browser).');
|
||||
if (result) {
|
||||
if(result){
|
||||
result = result + ' (local)';
|
||||
sequencer.saveSequence(result, sequencer.toString()); // 1.a study saveSequence
|
||||
sequencer.saveSequence(result, sequencer.toString());
|
||||
sequencer.loadModules();
|
||||
displayMessageOnSaveSequence();
|
||||
refreshOptions();
|
||||
}
|
||||
}
|
||||
$('#saveButton').on('click', function () {
|
||||
// Different handlers triggered for different dropdown options.
|
||||
|
||||
let dropDownValue = $('#selectSaveOption option:selected').val();
|
||||
|
||||
if (dropDownValue == 'save-image') {
|
||||
$('.download-btn:last()').trigger('click');
|
||||
}
|
||||
else if (dropDownValue == 'save-gif') {
|
||||
handleSavePNG();
|
||||
}
|
||||
else if (dropDownValue == 'save-seq') {
|
||||
saveSequence();
|
||||
} else if(dropDownValue == 'save-pdf') {
|
||||
savePDF(getLastImage());
|
||||
}
|
||||
else if (dropDownValue == 'save-to-publiclab.org' ){
|
||||
SaveToPubliclab();
|
||||
}
|
||||
});
|
||||
|
||||
let isWorkingOnGifGeneration = false;
|
||||
var isWorkingOnGifGeneration = false;
|
||||
|
||||
$('.js-view-as-gif').on('click', function (event) { // GIF generation and display
|
||||
if (isWorkingOnGifGeneration) return; // Prevent multiple button clicks
|
||||
$('.js-view-as-gif').on('click', function(event) {
|
||||
// Prevent user from triggering generation multiple times
|
||||
if (isWorkingOnGifGeneration) return;
|
||||
|
||||
isWorkingOnGifGeneration = true;
|
||||
|
||||
@@ -182,22 +116,39 @@ window.onload = function () {
|
||||
button.innerHTML = '<i class="fa fa-circle-o-notch fa-spin"></i>';
|
||||
|
||||
try {
|
||||
// Get GIF resources from previous steps
|
||||
let options = getGifResources();
|
||||
// Select all images from previous steps
|
||||
var imgs = document.getElementsByClassName('step-thumbnail');
|
||||
|
||||
gifshot.createGIF(options, function (obj) { // GIF generation
|
||||
var imgSrcs = [];
|
||||
|
||||
for (var i = 0; i < imgs.length; i++) {
|
||||
imgSrcs.push(imgs[i].src);
|
||||
}
|
||||
|
||||
var options = {
|
||||
'gifWidth': imgs[0].width,
|
||||
'gifHeight': imgs[0].height,
|
||||
'images': imgSrcs,
|
||||
'frameDuration': 7,
|
||||
};
|
||||
|
||||
gifshot.createGIF(options, function(obj) {
|
||||
if (!obj.error) {
|
||||
// Final GIF encoded with base64 format
|
||||
// Final gif encoded with base64 format
|
||||
var image = obj.image;
|
||||
var animatedImage = document.createElement('img');
|
||||
|
||||
animatedImage.id = 'gif_element';
|
||||
animatedImage.src = image;
|
||||
|
||||
let modal = $('#js-download-gif-modal');
|
||||
|
||||
$('#js-download-as-gif-button').one('click', function () {
|
||||
downloadGif(image); // Trigger GIF download
|
||||
var modal = $('#js-download-gif-modal');
|
||||
|
||||
$('#js-download-as-gif-button').one('click', function() {
|
||||
// Trigger download
|
||||
download(image, 'index.gif', 'image/gif');
|
||||
|
||||
// Close modal
|
||||
modal.modal('hide');
|
||||
});
|
||||
|
||||
@@ -209,6 +160,7 @@ window.onload = function () {
|
||||
// Insert image
|
||||
gifContainer.appendChild(animatedImage);
|
||||
|
||||
|
||||
// Open modal
|
||||
modal.modal();
|
||||
|
||||
@@ -223,96 +175,11 @@ window.onload = function () {
|
||||
button.disabled = false;
|
||||
button.innerHTML = 'View GIF';
|
||||
isWorkingOnGifGeneration = false;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function getGifResources() {
|
||||
// Returns an object with specific gif options
|
||||
let imgs = document.getElementsByClassName('step-thumbnail');
|
||||
var imgSrcs = [];
|
||||
|
||||
// Pushes image sources of all the modules in the DOM
|
||||
for (var i = 0; i < imgs.length; i++) {
|
||||
imgSrcs.push(imgs[i].src);
|
||||
}
|
||||
|
||||
var options = { // GIF frame options
|
||||
'gifWidth': imgs[0].width,
|
||||
'gifHeight': imgs[0].height,
|
||||
'images': imgSrcs,
|
||||
'frameDuration': 7,
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function handleSavePNG() {
|
||||
let options = getGifResources();
|
||||
gifshot.createGIF(options, function(obj){
|
||||
|
||||
downloadGif(obj.image);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
download(image, 'index.gif', 'image/gif'); // Downloadjs library function
|
||||
}
|
||||
|
||||
function SaveToPubliclab() {
|
||||
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
|
||||
// image selection and drag/drop handling from examples/lib/imageSelection.js
|
||||
sequencer.setInputStep({
|
||||
dropZoneSelector: '#dropzone',
|
||||
fileInputSelector: '#fileInput',
|
||||
@@ -323,31 +190,31 @@ window.onload = function () {
|
||||
var util = intermediateHtmlStepUi(sequencer);
|
||||
step.output.src = reader.result;
|
||||
sequencer.run({ index: 0 });
|
||||
if (typeof step.options !== 'undefined')
|
||||
if(typeof step.options !== 'undefined')
|
||||
step.options.step.imgElement.src = reader.result;
|
||||
else
|
||||
step.imgElement.src = reader.result;
|
||||
|
||||
insertPreview.updatePreviews(reader.result, document.querySelector('#addStep'));
|
||||
DefaultHtmlStepUi(sequencer).updateDimensions(step);
|
||||
insertPreview.updatePreviews(reader.result, '#addStep');
|
||||
insertPreview.updatePreviews(sequencer.steps[0].imgElement.src, '.insertDiv');
|
||||
},
|
||||
onTakePhoto: function (url) {
|
||||
var step = sequencer.steps[0];
|
||||
step.output.src = url;
|
||||
sequencer.run({ index: 0 });
|
||||
if (typeof step.options !== 'undefined')
|
||||
if(typeof step.options !== 'undefined')
|
||||
step.options.step.imgElement.src = url;
|
||||
else
|
||||
step.imgElement.src = url;
|
||||
insertPreview.updatePreviews(url, document.querySelector('#addStep'));
|
||||
insertPreview.updatePreviews(url, '#addStep');
|
||||
insertPreview.updatePreviews(sequencer.steps[0].imgElement.src, '.insertDiv');
|
||||
}
|
||||
});
|
||||
|
||||
setupCache();
|
||||
|
||||
if (urlHash.getUrlHashParameter('src')) { // Gets the sequence from the URL
|
||||
insertPreview.updatePreviews(urlHash.getUrlHashParameter('src'), document.querySelector('#addStep'));
|
||||
if (urlHash.getUrlHashParameter('src')) {
|
||||
insertPreview.updatePreviews(urlHash.getUrlHashParameter('src'), '#addStep');
|
||||
} else {
|
||||
insertPreview.updatePreviews('images/tulips.png', document.querySelector('#addStep'));
|
||||
insertPreview.updatePreviews('images/tulips.png', '#addStep');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,12 +33,9 @@
|
||||
<script src="../node_modules/gifshot/dist/gifshot.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- 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="../node_modules/jspdf/dist/jspdf.min.js" type="text/javascript" ></script>
|
||||
|
||||
<!-- <script src="lib/scrollToTop.js"></script> -->
|
||||
<script src="lib/scrollToTop.js"></script>
|
||||
<script src="../node_modules/selectize/dist/js/standalone/selectize.min.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -56,17 +53,15 @@
|
||||
<link href="./selectize.default.css" rel="stylesheet">
|
||||
<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">
|
||||
|
||||
<header class="text-center" style="min-width: 450px">
|
||||
<header class="text-center">
|
||||
<h1><a href="/" target='_blank' class="name-header">Image Sequencer</a></h1>
|
||||
<p>
|
||||
A pure JavaScript sequential image processing system, inspired by storyboards. Instead of modifying the original
|
||||
image, it
|
||||
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>
|
||||
Open Source
|
||||
@@ -75,11 +70,10 @@
|
||||
</a>
|
||||
by <a href="https://publiclab.org" title="Publiclab Website"><i class="fa fa-globe"></i> Publiclab</a>
|
||||
</p>
|
||||
<span id="version-number-top-right"></span>
|
||||
</header>
|
||||
|
||||
<div id="dropzone" class="dropzone">
|
||||
<p id="dropzone-text">
|
||||
<p>
|
||||
<i>Select or drag in an image to start!</i>
|
||||
</p>
|
||||
<center>
|
||||
@@ -97,7 +91,7 @@
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
<p class="alert alert-success savesequencemsg">Saved Sequence Success. Sequence can be found among other modules in browser's localStorage.
|
||||
<p class="alert alert-success savesequencemsg">Saved Sequence Success. Sequence can be found among other modules.
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
@@ -145,16 +139,16 @@
|
||||
<p>Crop</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="row center-align">
|
||||
<div class="col-md-8">
|
||||
|
||||
<select id="selectStep" class="text-center">
|
||||
<select id="selectStep">
|
||||
<!-- The default null selection has been appended manually in demo.js
|
||||
This is because the options in select are overritten when options are appended.-->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<button class="btn btn-primary btn-lg" name="add" id="add-step-btn">Add Step</button></div>
|
||||
<div class="col-md-4">
|
||||
<button class="btn btn-success btn-lg" name="add" id="add-step-btn">Add Step</button></div>
|
||||
</div>
|
||||
<div class="row center-align">
|
||||
<button id="resetButton" class="btn btn-default btn-lg"
|
||||
@@ -193,13 +187,11 @@
|
||||
<div class="panel-body">
|
||||
<div style="text-align:center;">
|
||||
<h2 style="margin-top:20px">Save</h2>
|
||||
<select class="form-control input-md mouse" id="selectSaveOption" style="margin-top:20px">
|
||||
<option value="save-image">Save as PNG</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-string">Save sequence string</option>
|
||||
<option value="save-to-publiclab.org">Save to PublicLab.org</option>>
|
||||
<select class="form-control input-md" id="selectSaveOption" style="margin-top:20px">
|
||||
<option>Save as PNG</option>
|
||||
<option>Save as GIF (all steps)</option>
|
||||
<option>Save sequence</option>
|
||||
<option>Save sequence string</option>
|
||||
</select>
|
||||
<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>
|
||||
@@ -231,9 +223,6 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p id="version-number-text">Unable to load version number</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<button id="move-up"><i class="fa fa-arrow-circle-o-up"></i></button>
|
||||
|
||||
@@ -1,41 +1,7 @@
|
||||
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) {
|
||||
// Register the service worker.
|
||||
navigator.serviceWorker.register('sw.js', { scope: '/examples/' })
|
||||
.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;
|
||||
installingWorker.onstatechange = () => {
|
||||
console.log(installingWorker);
|
||||
@@ -48,17 +14,6 @@ var setupCache = function() {
|
||||
.catch(function(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) {
|
||||
@@ -79,11 +34,6 @@ var setupCache = function() {
|
||||
}
|
||||
location.reload();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = setupCache;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var urlHash = require('./urlHash.js');
|
||||
insertPreview = require('./insertPreview.js');
|
||||
function DefaultHtmlSequencerUi(_sequencer, options) {
|
||||
|
||||
options = options || {};
|
||||
@@ -34,11 +33,6 @@ function DefaultHtmlSequencerUi(_sequencer, options) {
|
||||
|
||||
function removeStepUi() {
|
||||
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 });
|
||||
// remove from URL hash too
|
||||
urlHash.setUrlHashParameter('steps', sequencer.toString());
|
||||
|
||||
@@ -11,35 +11,29 @@
|
||||
const intermediateHtmlStepUi = require('./intermediateHtmlStepUi.js'),
|
||||
urlHash = require('./urlHash.js'),
|
||||
_ = require('lodash'),
|
||||
insertPreview = require('./insertPreview.js');
|
||||
mapHtmlTypes = require('./mapHtmltypes'),
|
||||
scopeQuery = require('./scopeQuery'),
|
||||
isGIF = require('../../src/util/isGif');
|
||||
scopeQuery = require('./scopeQuery');
|
||||
|
||||
function DefaultHtmlStepUi(_sequencer, options) {
|
||||
let $step, $stepAll;
|
||||
|
||||
options = options || {};
|
||||
var stepsEl = options.stepsEl || document.querySelector('#steps');
|
||||
var selectStepSel = options.selectStepSel = options.selectStepSel || '#selectStep';
|
||||
|
||||
function onSetup(step, stepOptions) {
|
||||
|
||||
if (step.options && 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="panel panel-default">\
|
||||
<div class="panel-heading">\
|
||||
<div class="trash-container pull-right">\
|
||||
<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>\
|
||||
<div class="trash-container pull-right"></div>\
|
||||
<h3 class="panel-title">' +
|
||||
'<span class="toggle mouse">' + step.name + ' <span class="caret toggleIcon rotated"></span>\
|
||||
'<span class="toggle">' + 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>\
|
||||
</h3>\
|
||||
</div>\
|
||||
@@ -48,7 +42,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
<div class="row step">\
|
||||
<div class="col-md-4 details container-fluid">\
|
||||
<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>\
|
||||
</div>\
|
||||
<div class="col-md-8 cal collapse in step-column">\
|
||||
@@ -65,7 +59,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
</div>';
|
||||
|
||||
var tools =
|
||||
'<div class="trash" style="display: inline-block">\
|
||||
'<div class="trash">\
|
||||
<button confirm="Are you sure?" class="remove btn btn-default btn-xs">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
@@ -74,20 +68,21 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
var util = intermediateHtmlStepUi(_sequencer, step);
|
||||
|
||||
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.$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.$stepAll = scopeQuery.scopeSelectorAll(step.ui);
|
||||
let {$step, $stepAll} = step;
|
||||
$step = scopeQuery.scopeSelector(step.ui);
|
||||
$stepAll = scopeQuery.scopeSelectorAll(step.ui);
|
||||
step.ui.$step = $step;
|
||||
step.ui.$stepAll = $stepAll;
|
||||
|
||||
step.linkElements = step.ui.querySelectorAll('a'); // All the anchor tags in the step UI
|
||||
step.imgElement = $step('a img.img-thumbnail')[0]; // The output image
|
||||
step.linkElements = step.ui.querySelectorAll('a');
|
||||
step.imgElement = $step('a img.img-thumbnail')[0];
|
||||
|
||||
if (_sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
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) {
|
||||
var isInput = inputs.hasOwnProperty(paramName);
|
||||
@@ -107,10 +102,10 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
else {
|
||||
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 +=
|
||||
'<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 +
|
||||
'" name="' +
|
||||
paramName +
|
||||
@@ -118,7 +113,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
paramVal + '">' + '<span class="input-group-addon"><i></i></span>' +
|
||||
'</div>';
|
||||
}
|
||||
else { // Non color-picker input types
|
||||
else { // use this if the the field isn't color-picker
|
||||
html =
|
||||
'<input class="form-control target" type="' +
|
||||
inputDesc.type +
|
||||
@@ -128,7 +123,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
paramVal +
|
||||
'" placeholder ="' +
|
||||
(inputDesc.placeholder || '');
|
||||
|
||||
|
||||
if (inputDesc.type.toLowerCase() == 'range') {
|
||||
html +=
|
||||
'"min="' +
|
||||
@@ -160,10 +155,10 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
</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>'
|
||||
);
|
||||
$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" >\
|
||||
<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" >\
|
||||
@@ -178,47 +173,31 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
parser.parseFromString(tools, 'text/html').querySelector('div')
|
||||
);
|
||||
|
||||
$stepAll('.remove').on('click', function() {notify('Step Removed', 'remove-notification');}); // Notification on removal of a step
|
||||
$step('.insert-step').on('click', function() { util.insertStep(step.ID); }); // Insert a step in between the sequence
|
||||
$stepAll('.remove').on('click', function() {notify('Step Removed', 'remove-notification');});
|
||||
$stepAll('.insert-step').on('click', function() { util.insertStep(step.ID); });
|
||||
// Insert the step's UI in the right place
|
||||
|
||||
if (stepOptions.index == _sequencer.steps.length) {
|
||||
stepsEl.appendChild(step.ui);
|
||||
$('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true);
|
||||
if($('#steps .step-container:nth-last-child(2)'))
|
||||
$('#steps .step-container:nth-last-child(2) .insert-step').prop('disabled', false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
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 {
|
||||
$('#load-image').append(step.ui); // Default UI without extra tools for the first step(load image)
|
||||
|
||||
$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); });
|
||||
$('#load-image').append(step.ui);
|
||||
}
|
||||
$step('.toggle').on('click', () => { // Step container dropdown
|
||||
$step('.toggle').on('click', () => {
|
||||
$step('.toggleIcon').toggleClass('rotated');
|
||||
$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(); });
|
||||
$stepAll('#color-picker').colorpicker();
|
||||
|
||||
function saveOptions(e) { // 1. SAVE OPTIONS
|
||||
function saveOptions(e) {
|
||||
e.preventDefault();
|
||||
if (optionsChanged){
|
||||
$step('div.details')
|
||||
@@ -231,23 +210,15 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
});
|
||||
_sequencer.run({ index: step.index - 1 });
|
||||
|
||||
// Modify the URL hash
|
||||
// modify the url hash
|
||||
urlHash.setUrlHashParameter('steps', _sequencer.toString());
|
||||
// Disable the save button
|
||||
// disable the save button
|
||||
$step('.btn-save').prop('disabled', true);
|
||||
optionsChanged = false;
|
||||
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) {
|
||||
var inputChanged = !(isNaN(initValue) || isNaN(currentValue) ? currentValue === initValue : currentValue - initValue === 0);
|
||||
changedInputs += hasChangedBefore ? inputChanged ? 0 : -1 : inputChanged ? 1 : 0;
|
||||
@@ -278,21 +249,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() {
|
||||
@@ -301,20 +257,17 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
}
|
||||
|
||||
|
||||
function onDraw({$step, $stepAll}) {
|
||||
function onDraw() {
|
||||
$step('.load').show();
|
||||
$step('img').hide();
|
||||
$stepAll('.load-spin').show();
|
||||
}
|
||||
|
||||
function onComplete(step) {
|
||||
let {$step, $stepAll} = step;
|
||||
$step('img').show();
|
||||
$stepAll('.load-spin').hide();
|
||||
$step('.load').hide();
|
||||
|
||||
$stepAll('.download-btn').off('click');
|
||||
|
||||
step.imgElement.src = (step.name == 'load-image') ? step.output.src : step.output;
|
||||
var imgthumbnail = $step('.img-thumbnail').getDomElem();
|
||||
for (let index = 0; index < step.linkElements.length; index++) {
|
||||
@@ -322,23 +275,28 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
step.linkElements[index].href = step.imgElement.src;
|
||||
}
|
||||
|
||||
// TODO: use a generalized version of this.
|
||||
// TODO: use a generalized version of this
|
||||
function fileExtension(output) {
|
||||
return output.split('/')[1].split(';')[0];
|
||||
}
|
||||
|
||||
$stepAll('.download-btn').on('click', () => {
|
||||
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', step.output);
|
||||
element.setAttribute('download', step.name + '.' + fileExtension(step.imgElement.src));
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
for (let index = 0; index < step.linkElements.length; index++){
|
||||
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', step.linkElements[index].href);
|
||||
element.setAttribute('download', step.name + '.' + fileExtension(step.imgElement.src));
|
||||
element.style.display = 'none';
|
||||
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)) {
|
||||
var inputs = _sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = _sequencer.modulesInfo(step.name).outputs;
|
||||
@@ -350,7 +308,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
.data('initValue', step.options[i]);
|
||||
if (inputs[i].type.toLowerCase() === 'select')
|
||||
$step('div[name="' + i + '"] select')
|
||||
.val(String(step.options[i]))
|
||||
.val(step.options[i])
|
||||
.data('initValue', step.options[i]);
|
||||
}
|
||||
}
|
||||
@@ -360,41 +318,8 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
.val(step[i]);
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('[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) {
|
||||
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>`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @method imageHover
|
||||
* @description Handler to display image coordinates on hover.
|
||||
* @param {Object} step Current step variable
|
||||
* @returns {Null}
|
||||
*/
|
||||
function imageHover(step){
|
||||
|
||||
var img = $(step.imgElement);
|
||||
@@ -417,42 +342,27 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
function onRemove(step) {
|
||||
step.ui.remove();
|
||||
$('#steps .step-container:nth-last-child(1) .insert-step').prop('disabled', true);
|
||||
|
||||
// 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
|
||||
});
|
||||
$('div[class*=imgareaselect-]').remove();
|
||||
}
|
||||
|
||||
function getPreview() {
|
||||
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){
|
||||
if ($('#' + id).length == 0) {
|
||||
var notification = document.createElement('span');
|
||||
notification.innerHTML = ' <i class="fa fa-info-circle" aria-hidden="true"></i> ' + msg ;
|
||||
notification.id = id;
|
||||
notification.classList.add('notification');
|
||||
|
||||
|
||||
$('body').append(notification);
|
||||
}
|
||||
|
||||
|
||||
$('#' + id).fadeIn(500).delay(200).fadeOut(500);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
getPreview: getPreview,
|
||||
onSetup: onSetup,
|
||||
@@ -460,8 +370,7 @@ function DefaultHtmlStepUi(_sequencer, options) {
|
||||
onRemove: onRemove,
|
||||
onDraw: onDraw,
|
||||
notify: notify,
|
||||
imageHover: imageHover,
|
||||
updateDimensions: updateDimensions
|
||||
imageHover: imageHover
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Generate downscaled preview images for quick buttons.
|
||||
function generatePreview(previewStepName, customValues, path, DomNode) {
|
||||
function generatePreview(previewStepName, customValues, path, selector) {
|
||||
|
||||
var previewSequencer = ImageSequencer();
|
||||
function insertPreview(src) {
|
||||
var img = document.createElement('img');
|
||||
@@ -8,10 +8,9 @@ function generatePreview(previewStepName, customValues, path, DomNode) {
|
||||
img.src = src;
|
||||
$(img).css('max-width', '200%');
|
||||
$(img).css('transform', 'translateX(-20%)');
|
||||
$(DomNode.querySelector('.radio-group')).find('.radio').each(function() {
|
||||
if ($(this).attr('data-value') === previewStepName) {
|
||||
$(this).find('img').remove();
|
||||
$(this).append(img);
|
||||
$(selector + ' .radio-group').find('div').each(function() {
|
||||
if ($(this).find('div').attr('data-value') === previewStepName) {
|
||||
$(this).find('div').append(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -30,8 +29,8 @@ function generatePreview(previewStepName, customValues, path, DomNode) {
|
||||
previewSequencer.loadImage(path, loadPreview);
|
||||
}
|
||||
|
||||
function updatePreviews(src, DomNode) {
|
||||
$(DomNode).find('img').remove();
|
||||
function updatePreviews(src, selector) {
|
||||
$(selector + ' img').remove();
|
||||
|
||||
var previewSequencerSteps = {
|
||||
'resize': '125%',
|
||||
@@ -42,20 +41,19 @@ function updatePreviews(src, DomNode) {
|
||||
'crop': {
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'w': '50%',
|
||||
'h': '50%',
|
||||
'w': '(50%)',
|
||||
'h': '(50%)',
|
||||
'noUI': true
|
||||
}
|
||||
};
|
||||
|
||||
var img = new Image();
|
||||
|
||||
img.onload = function(){
|
||||
var height = img.height;
|
||||
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.
|
||||
percentage = Math.max((80 / width) * 100, percentage); // Make sure that one dimension doesn't resize greater, leading distorting preview-area fitting.
|
||||
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.ceil(percentage);
|
||||
|
||||
var sequencer = ImageSequencer();
|
||||
@@ -64,7 +62,7 @@ function updatePreviews(src, DomNode) {
|
||||
this.addSteps('resize', {['resize']: percentage + '%'});
|
||||
this.run((src)=>{
|
||||
Object.keys(previewSequencerSteps).forEach(function (step, index) {
|
||||
generatePreview(step, Object.values(previewSequencerSteps)[index], src, DomNode);
|
||||
generatePreview(step, Object.values(previewSequencerSteps)[index], src, selector);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -76,4 +74,4 @@ function updatePreviews(src, DomNode) {
|
||||
module.exports = {
|
||||
generatePreview : generatePreview,
|
||||
updatePreviews : updatePreviews
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
var urlHash = require('./urlHash.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 stepUI() {
|
||||
// Basic markup for the selector
|
||||
return '<div class="row insertDiv collapse">\
|
||||
<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>\
|
||||
@@ -64,7 +55,7 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
|
||||
</select>\
|
||||
<div>\
|
||||
<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>\
|
||||
@@ -73,13 +64,15 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @method toggleDiv
|
||||
* @description Toggles the module selector dropdown.
|
||||
* @param {Object} $step $step util function
|
||||
* @param {Fucntion} callback Optional callback function
|
||||
* @returns {Null}
|
||||
*/
|
||||
|
||||
function selectNewStepUi($step) {
|
||||
var insertSelect = $step('.insert-step-select');
|
||||
var m = insertSelect.val();
|
||||
$step('.insertDiv .info').html(_sequencer.modulesInfo(m).description);
|
||||
$step('.insertDiv .add-step-btn').prop('disabled', false);
|
||||
}
|
||||
|
||||
|
||||
var toggleDiv = function($step, callback = function(){}){
|
||||
$step('.insertDiv').collapse('toggle');
|
||||
if ($step('.insert-text').css('display') != 'none'){
|
||||
@@ -90,19 +83,13 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method insertStep
|
||||
* @description Handler to insert selected module in the sequence
|
||||
* @returns {Null}
|
||||
*/
|
||||
insertStep = function (id) {
|
||||
const $step = step.$step,
|
||||
$stepAll = step.$stepAll;
|
||||
const $step = step.ui.$step,
|
||||
$stepAll = step.ui.$stepAll;
|
||||
var modulesInfo = _sequencer.modulesInfo();
|
||||
var parser = new DOMParser();
|
||||
var addStepUI = stepUI();
|
||||
addStepUI = parser.parseFromString(addStepUI, 'text/html').querySelector('div');
|
||||
|
||||
if ($step('.insertDiv').length > 0){
|
||||
toggleDiv($step);
|
||||
}
|
||||
@@ -113,56 +100,60 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
|
||||
addStepUI
|
||||
);
|
||||
toggleDiv($step, function(){
|
||||
if (step.name === 'load-image') insertPreview.updatePreviews(step.output.src, $step('.insertDiv').getDomElem());
|
||||
else insertPreview.updatePreviews(step.output, $step('.insertDiv').getDomElem());
|
||||
insertPreview.updatePreviews(step.output, '.insertDiv');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$step('.insertDiv .close-insert-box').off('click').on('click', function(){
|
||||
toggleDiv($step);
|
||||
$step('.insertDiv').removeClass('insertDiv');
|
||||
});
|
||||
|
||||
$step('.insertDiv .close-insert-box').off('click').on('click', function(){toggleDiv(function(){});});
|
||||
|
||||
var insertStepSelect = $step('.insert-step-select');
|
||||
insertStepSelect.html('');
|
||||
|
||||
// Add modules to the insertStep dropdown
|
||||
for (var m in modulesInfo) {
|
||||
if (modulesInfo[m] && modulesInfo[m].name)
|
||||
if (modulesInfo[m] !== undefined)
|
||||
insertStepSelect.append(
|
||||
'<option value="' + m + '">' + modulesInfo[m].name + '</option>'
|
||||
);
|
||||
}
|
||||
|
||||
insertStepSelect.selectize({
|
||||
sortField: 'text'
|
||||
});
|
||||
|
||||
$('.insertDiv .radio-group .radio').on('click', function () {
|
||||
var newStepName = $(this).attr('data-value'); // Get the name of the module to be inserted
|
||||
id = $($step('.insertDiv').parents()[3]).prevAll().length;
|
||||
insert(id, $step, newStepName); // Insert the selected module
|
||||
$step('.inserDiv .add-step-btn').prop('disabled', true);
|
||||
|
||||
insertStepSelect.append('<option value="" disabled selected>Select a Module</option>');
|
||||
$step('.insertDiv .radio-group .radio').on('click', function () {
|
||||
$(this).parent().find('.radio').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
newStep = $(this).attr('data-value');
|
||||
$step('.insert-step-select').val(newStep);
|
||||
selectNewStepUi($step);
|
||||
insert(id, $step);
|
||||
$(this).removeClass('selected');
|
||||
});
|
||||
|
||||
$step('.insertDiv .add-step-btn').on('click', function () {
|
||||
var newStepName = insertStepSelect.val();
|
||||
id = $($step('.insertDiv').parents()[3]).prevAll().length;
|
||||
insert(id, $step, newStepName); });
|
||||
insertStepSelect.on('change', () => {selectNewStepUi($step);});
|
||||
$step('.insertDiv .add-step-btn').on('click', function () { insert(id, $step); });
|
||||
};
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
|
||||
options = options || {};
|
||||
var insertStepSelect = $step('.insert-step-select');
|
||||
if (insertStepSelect.val() == 'none') return;
|
||||
|
||||
var newStepName = insertStepSelect.val();
|
||||
toggleDiv($step);
|
||||
$step('.insertDiv').removeClass('insertDiv');
|
||||
_sequencer.insertSteps(id + 1, newStepName).run({ index: id });
|
||||
var sequenceLength = 1;
|
||||
if (sequencer.sequences[newStepName]) {
|
||||
sequenceLength = sequencer.sequences[newStepName].length;
|
||||
} else if (sequencer.modules[newStepName][1]['length']) {
|
||||
sequenceLength = sequencer.modules[newStepName][1]['length'];
|
||||
}
|
||||
_sequencer
|
||||
.insertSteps(id + 1, newStepName).run({ index: id });
|
||||
|
||||
// add to URL hash too
|
||||
urlHash.setUrlHashParameter('steps', _sequencer.toString());
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -170,3 +161,4 @@ function IntermediateHtmlStepUi(_sequencer, step, options) {
|
||||
};
|
||||
}
|
||||
module.exports = IntermediateHtmlStepUi;
|
||||
|
||||
|
||||
@@ -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){
|
||||
var htmlType;
|
||||
switch(inputInfo.type.toLowerCase()){
|
||||
@@ -24,9 +20,9 @@ function mapHtmlTypes(inputInfo){
|
||||
htmlType = 'text';
|
||||
break;
|
||||
}
|
||||
var response = Object.assign({}, inputInfo);
|
||||
var response = inputInfo;
|
||||
response.type = htmlType;
|
||||
return response;
|
||||
}
|
||||
|
||||
module.exports = mapHtmlTypes;
|
||||
module.exports = mapHtmlTypes;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @method $scope
|
||||
* @param {"DOMNode"} scope A DOM Node as the scope
|
||||
* @returns {Function} Constructor for the scopeSelector Object.
|
||||
*/
|
||||
function $scope(scope) {
|
||||
return function(queryString){
|
||||
@@ -25,7 +24,6 @@ function $scope(scope) {
|
||||
/**
|
||||
* @method $scopeAll
|
||||
* @param {"DOMNode"} scope A DOM Node as the scope
|
||||
* @returns {Function} Constructor for the scopeSelectorAll Object.
|
||||
*/
|
||||
function $scopeAll(scope){
|
||||
return function(queryString){
|
||||
@@ -49,8 +47,7 @@ function $scopeAll(scope){
|
||||
/**
|
||||
* @method scopeSelector
|
||||
* @description A scoped jQuery selector
|
||||
* @param {"DOMNode"} scope A DOM Node as the scope
|
||||
* @returns {Function}
|
||||
* @param {"DOMNode"} scope DOM Node as the scope
|
||||
*/
|
||||
function scopeSelector(scope){
|
||||
return $scope(scope);
|
||||
@@ -59,8 +56,7 @@ function scopeSelector(scope){
|
||||
/**
|
||||
* @method scopeSelectorAll
|
||||
* @description A scoped jQuery multiple selector
|
||||
* @param {"DOMNode} scope A DOM Node as the scope
|
||||
* @returns {Function}
|
||||
* @param {"DOMNode} scope DOM Node as the scope
|
||||
*/
|
||||
function scopeSelectorAll(scope){
|
||||
return $scopeAll(scope);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
const staticCacheName = 'image-sequencer-static-v3.5.1';
|
||||
const staticCacheName = 'image-sequencer-static-v3';
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
launch: {
|
||||
headless: process.env.HEADLESS !== 'false',
|
||||
},
|
||||
server: {
|
||||
command: 'grunt serve',
|
||||
port:3000,
|
||||
launchTimeout: 5000000,
|
||||
},
|
||||
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'jest-puppeteer',
|
||||
testRegex: './*\\.test\\.js$',
|
||||
verbose: true,
|
||||
};
|
||||
|
||||
7085
package-lock.json
generated
7085
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -5,14 +5,9 @@
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
|
||||
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/*.js | tap-spec;",
|
||||
"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": "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\"",
|
||||
"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 && npm rebuild --build-from-source && grunt build",
|
||||
"setup": "npm i && npm i -g grunt grunt-cli && grunt build",
|
||||
"start": "grunt serve"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -37,40 +32,37 @@
|
||||
"dependencies": {
|
||||
"atob": "^2.1.2",
|
||||
"base64-img": "^1.0.4",
|
||||
"bootstrap": "^3.4.1",
|
||||
"bootstrap": "~3.4.0",
|
||||
"bootstrap-colorpicker": "^2.5.3",
|
||||
"buffer": "~5.6.0",
|
||||
"commander": "^4.0.1",
|
||||
"buffer": "~5.2.1",
|
||||
"commander": "^2.11.0",
|
||||
"compressorjs": "^1.0.5",
|
||||
"data-uri-to-buffer": "^3.0.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"downloadjs": "^1.4.7",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint": "^5.16.0",
|
||||
"fisheyegl": "^0.1.2",
|
||||
"font-awesome": "~4.7.0",
|
||||
"geotiff": "^1.0.0-beta.6",
|
||||
"get-pixels": "~3.3.0",
|
||||
"gifshot": "^0.4.5",
|
||||
"glfx": "0.0.4",
|
||||
"gpu.js": "^2.3.1",
|
||||
"gpu.js": "^2.0.0-rc.12",
|
||||
"image-sequencer-invert": "^1.0.0",
|
||||
"imagejs": "0.0.9",
|
||||
"imagemin": "^7.0.1",
|
||||
"imagemin": "^7.0.0",
|
||||
"imagemin-jpegtran": "^6.0.0",
|
||||
"imagemin-pngquant": "^8.0.0",
|
||||
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2",
|
||||
"istanbul": "^0.4.5",
|
||||
"jasmine": "^3.4.0",
|
||||
"jpegtran-bin": "^4.0.0",
|
||||
"jquery": "^3.3.1",
|
||||
"jsdom": "^15.0.0",
|
||||
"jspdf": "^1.5.3",
|
||||
"jsqr": "^1.1.1",
|
||||
"lodash": "^4.17.11",
|
||||
"ndarray": "^1.0.18",
|
||||
"opencv.js": "^1.2.1",
|
||||
"ora": "^4.0.3",
|
||||
"ora": "^3.0.0",
|
||||
"pace": "0.0.4",
|
||||
"pngquant-bin": "^5.0.2",
|
||||
"puppeteer": "^1.14.0",
|
||||
"qrcode": "^1.3.3",
|
||||
"readline-sync": "^1.4.7",
|
||||
@@ -85,24 +77,21 @@
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.4.3",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.2.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "16.5.0",
|
||||
"eslint": "^6.1.0",
|
||||
"browserify": "16.2.3",
|
||||
"eslint": "^5.16.0",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-browser-sync": "^2.2.0",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-uglify-es": "^3.3.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-text-replace": "^0.4.0",
|
||||
"husky": "^3.0.5",
|
||||
"husky": "^2.2.0",
|
||||
"image-filter-core": "~2.0.2",
|
||||
"image-filter-threshold": "~2.0.1",
|
||||
"jasmine-core": "^3.3.0",
|
||||
"jasmine-jquery": "^2.1.1",
|
||||
"jasmine-spec-reporter": "^4.2.1",
|
||||
"jest": "^25.1.0",
|
||||
"jest-puppeteer": "^4.3.0",
|
||||
"lint-staged": "^10.0.3",
|
||||
"lint-staged": "^9.1.0",
|
||||
"looks-same": "^7.0.0",
|
||||
"matchdep": "^2.0.0",
|
||||
"tap-spec": "^5.0.0",
|
||||
@@ -119,4 +108,4 @@
|
||||
"bin": {
|
||||
"sequencer": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,8 @@ if (typeof window !== 'undefined') { isBrowser = true; }
|
||||
else { var isBrowser = false; }
|
||||
require('./util/getStep.js');
|
||||
|
||||
/**
|
||||
* @method ImageSequencer
|
||||
* @param {Object|Float32Array} options Optional options
|
||||
* @returns {Object}
|
||||
*/
|
||||
ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
var str = require('./Strings.js')(this.steps, modulesInfo, addSteps, copy);
|
||||
|
||||
var sequencer = (this.name == 'ImageSequencer') ? this : this.sequencer;
|
||||
options = options || {};
|
||||
options.inBrowser = options.inBrowser === undefined ? isBrowser : options.inBrowser;
|
||||
@@ -19,12 +12,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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) {
|
||||
if (options.ui != 'none') {
|
||||
if (arguments.length == 1) console.log(arguments[0]);
|
||||
@@ -32,12 +19,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) {
|
||||
if (!typeof (a) == 'object') return a;
|
||||
if (objTypeOf(a) == 'Array') return a.slice();
|
||||
@@ -70,10 +51,10 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
for (o in sequencer) {
|
||||
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) {
|
||||
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 +62,11 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
// if (options.imageSelect || options.inBrowser) addStep('image-select');
|
||||
// else if (options.imageUrl) loadImage(imageUrl);
|
||||
|
||||
/**
|
||||
* @method addSteps
|
||||
* @description Adds one of more steps to the sequence.
|
||||
* @return {Object}
|
||||
*/
|
||||
function addSteps() {
|
||||
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
|
||||
var args = [];
|
||||
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, '+');
|
||||
|
||||
inputlog.push({ method: 'addSteps', json_q: copy(json_q) });
|
||||
@@ -99,28 +75,17 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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) {
|
||||
// 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) {
|
||||
// var this_ = (this.name == "ImageSequencer") ? this : this.sequencer;
|
||||
//var this_ = (this.name == "ImageSequencer") ? this : this.sequencer;
|
||||
thisStep = ref.steps[index];
|
||||
thisStep.UI.onRemove(thisStep.options.step);
|
||||
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() {
|
||||
var indices;
|
||||
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
|
||||
@@ -136,11 +101,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method insertSteps
|
||||
* @description Inserts steps at the specified index
|
||||
* @returns {Object}
|
||||
*/
|
||||
function insertSteps() {
|
||||
var this_ = (this.name == 'ImageSequencer') ? this : this.sequencer;
|
||||
var args = [];
|
||||
@@ -156,11 +116,8 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method run
|
||||
* @param {Object} config Object which contains the runtime configuration like progress bar information and index from which the sequencer should run.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
// Config is an object which contains the runtime configuration like progress bar
|
||||
// information and index from which the sequencer should run
|
||||
function run(config) {
|
||||
var progressObj, index = 0;
|
||||
config = config || { mode: 'no-arg' };
|
||||
@@ -178,7 +135,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
var callback = function() { };
|
||||
for (var arg in args)
|
||||
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');
|
||||
|
||||
@@ -187,11 +144,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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() {
|
||||
var args = [];
|
||||
var prevSteps = this.getSteps().slice(1).map(step=>step.options.name);
|
||||
@@ -228,35 +180,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) {
|
||||
options = options || {};
|
||||
options.callback = options.callback || function() { };
|
||||
return require('./ReplaceImage')(this, selector, steps, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method getSteps
|
||||
* @description Returns the current sequence of steps
|
||||
* @returns {Object}
|
||||
*/
|
||||
//returns the steps added
|
||||
function getSteps(){
|
||||
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) {
|
||||
this.events = require('./ui/UserInterface')(UI);
|
||||
}
|
||||
@@ -265,12 +199,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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) {
|
||||
var modulesdata = {};
|
||||
if (name == 'load-image') return {};
|
||||
@@ -292,30 +220,23 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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) {
|
||||
|
||||
if (!options) {
|
||||
return this;
|
||||
|
||||
} else if (Array.isArray(options)) {
|
||||
// Contains the array of module and info
|
||||
// contains the array of module and info
|
||||
this.modules[name] = options;
|
||||
|
||||
} else if (options.func && options.info) {
|
||||
// Passed in options object
|
||||
// passed in options object
|
||||
this.modules[name] = [
|
||||
options.func, options.info
|
||||
];
|
||||
|
||||
} else if (options.path && !this.inBrowser) {
|
||||
// Load from path(only in node)
|
||||
// load from path(only in node)
|
||||
const module = [
|
||||
require(`${options.path}/Module.js`),
|
||||
require(`${options.path}/info.json`)
|
||||
@@ -325,13 +246,6 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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) {
|
||||
if (options.inBrowser) {
|
||||
// Not for browser context
|
||||
@@ -342,15 +256,8 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
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
|
||||
const sequence = str.stringToJSON(sequenceString);
|
||||
function saveSequence(name, sequenceString) {
|
||||
const sequence = stringToJSON(sequenceString);
|
||||
// Save the given sequence string as a module
|
||||
if (options.inBrowser) {
|
||||
// Inside the browser we save the meta-modules using the Web Storage API
|
||||
@@ -367,7 +274,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
}
|
||||
|
||||
function loadModules() {
|
||||
// loadModules function loads the modules and saved sequences.
|
||||
// This function loads the modules and saved sequences
|
||||
this.modules = require('./Modules');
|
||||
if (options.inBrowser)
|
||||
this.sequences = JSON.parse(window.localStorage.getItem('sequences'));
|
||||
@@ -375,9 +282,10 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
this.sequences = require('./SavedSequences.json');
|
||||
}
|
||||
|
||||
var str = require('./Strings.js')(this.steps, modulesInfo, addSteps, copy);
|
||||
|
||||
return {
|
||||
// Literals and objects
|
||||
//literals and objects
|
||||
name: 'ImageSequencer',
|
||||
options: options,
|
||||
inputlog: inputlog,
|
||||
@@ -387,7 +295,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
steps: steps,
|
||||
image: image,
|
||||
|
||||
// User functions
|
||||
//user functions
|
||||
loadImages: loadImages,
|
||||
loadImage: loadImages,
|
||||
addSteps: addSteps,
|
||||
@@ -416,11 +324,10 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
loadModules: loadModules,
|
||||
getSteps:getSteps,
|
||||
|
||||
// Other functions
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy,
|
||||
getImageDimensions: require('./util/getImageDimensions'),
|
||||
|
||||
setInputStep: require('./ui/SetInputStep')(sequencer)
|
||||
};
|
||||
|
||||
@@ -11,10 +11,8 @@ module.exports = {
|
||||
'canvas-resize': require('./modules/CanvasResize'),
|
||||
'channel': require('./modules/Channel'),
|
||||
'colorbar': require('./modules/Colorbar'),
|
||||
'color-halftone': require('./modules/ColorHalftone'),
|
||||
'color-temperature': require('./modules/ColorTemperature'),
|
||||
'colormap': require('./modules/Colormap'),
|
||||
'constrained-crop': require('./modules/ConstrainedCrop'),
|
||||
'contrast': require('./modules/Contrast'),
|
||||
'convolution': require('./modules/Convolution'),
|
||||
'crop': require('./modules/Crop'),
|
||||
@@ -32,8 +30,8 @@ module.exports = {
|
||||
'grid-overlay': require('./modules/GridOverlay'),
|
||||
'import-image': require('./modules/ImportImage'),
|
||||
'minify-image': require('./modules/MinifyImage'),
|
||||
// 'invert': require('image-sequencer-invert'),
|
||||
'invert': require('./modules/Invert'),
|
||||
'invert': require('image-sequencer-invert'),
|
||||
'matcher': require('./modules/Matcher'),
|
||||
'ndvi': require('./modules/Ndvi'),
|
||||
'ndvi-colormap': require('./modules/NdviColormap'),
|
||||
'noise-reduction': require('./modules/NoiseReduction'),
|
||||
|
||||
@@ -45,8 +45,6 @@ function Run(ref, json_q, callback, ind, progressObj) {
|
||||
|
||||
// This output is accessible by UI
|
||||
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.
|
||||
ref.steps[i].UI.onComplete(ref.steps[i].options.step);
|
||||
|
||||
@@ -1,58 +1,46 @@
|
||||
const _ = require('lodash'),
|
||||
parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
|
||||
module.exports = function AddQR(options, UI) {
|
||||
|
||||
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;
|
||||
getPixels = require('get-pixels');
|
||||
|
||||
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.overrideFlag = true;
|
||||
|
||||
var step = this;
|
||||
|
||||
function extraManipulation(pixels, setRenderState, generateOutput) {
|
||||
let iw = pixels.shape[0], // Width of Original Image
|
||||
ih = pixels.shape[1]; // Height of Original Image
|
||||
const oldPixels = _.cloneDeep(pixels);
|
||||
setRenderState(false); // Prevent rendering of final output image until extraManipulation completes.
|
||||
return getPixels(input.src, function(err, oldPixels) {
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
// 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 extraManipulation(pixels, generateOutput) {
|
||||
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, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,37 +1,48 @@
|
||||
const pixelSetter = require('../../util/pixelSetter.js'),
|
||||
getPixels = require('get-pixels'),
|
||||
QRCode = require('qrcode');
|
||||
module.exports = exports = function (options, pixels, oldPixels, cb) {
|
||||
|
||||
QRCode.toDataURL(options.qrCodeString, {width: options.size, scale: 1}, function (error, url) {
|
||||
module.exports = exports = function (options, pixels, oldPixels, callback) {
|
||||
var QRCode = require('qrcode');
|
||||
QRCode.toDataURL(options.qrCodeString, function (err, url) {
|
||||
var getPixels = require('get-pixels');
|
||||
getPixels(url, function (err, qrPixels) {
|
||||
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];
|
||||
var xe = width - options.size,
|
||||
ye = height - options.size;
|
||||
for (var m = 0; m < width; m++) {
|
||||
for (var n = 0; n < height; n++) {
|
||||
if (m >= xe && n >= ye) {
|
||||
pixels.set(m, n, 0, qrPixels.get(m - xe, n - ye, 0));
|
||||
pixels.set(m, n, 1, qrPixels.get(m - xe, n - ye, 1));
|
||||
pixels.set(m, n, 2, qrPixels.get(m - xe, n - ye, 2));
|
||||
pixels.set(m, n, 3, qrPixels.get(m - xe, n - ye, 3));
|
||||
}
|
||||
|
||||
const xe = Math.min(options.startingX, width - options.size), // Starting pixel coordinates
|
||||
ye = Math.min(options.startingY, height - options.size);
|
||||
else {
|
||||
pixels.set(m, n, 0, oldPixels.get(m, n, 0));
|
||||
pixels.set(m, n, 1, oldPixels.get(m, n, 1));
|
||||
pixels.set(m, n, 2, oldPixels.get(m, n, 2));
|
||||
pixels.set(m, n, 3, oldPixels.get(m, n, 3));
|
||||
}
|
||||
|
||||
for (let x = xe; x < Math.min(xe + options.size, width); x++) {
|
||||
for (let y = ye; y < Math.min(ye + options.size, height); y++) {
|
||||
pixelSetter(
|
||||
x,
|
||||
y,
|
||||
[
|
||||
qrPixels.get(x - xe, y - ye, 0),
|
||||
qrPixels.get(x - xe, y - ye, 1),
|
||||
qrPixels.get(x - xe, y - ye, 2),
|
||||
qrPixels.get(x - xe, y - ye, 3)
|
||||
],
|
||||
pixels
|
||||
);
|
||||
}
|
||||
}
|
||||
callback();
|
||||
|
||||
if(cb) cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,16 +12,6 @@
|
||||
"type": "string",
|
||||
"desc": "input string to generate QR code",
|
||||
"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"
|
||||
|
||||
@@ -42,19 +42,23 @@ module.exports = function Average(options, UI) {
|
||||
// report back and store average in metadata:
|
||||
options.step.metadata.averages = sum;
|
||||
|
||||
if (options.step.average === undefined) options.step.average = '';
|
||||
options.step.average += 'rgba(' + sum.join(', ') + ')';
|
||||
// TODO: refactor into a new "display()" method as per https://github.com/publiclab/image-sequencer/issues/242
|
||||
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;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {
|
||||
src: datauri,
|
||||
format: mimetype
|
||||
};
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
inBrowser: options.inBrowser,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
|
||||
@@ -3,11 +3,5 @@
|
||||
"description": "Average all pixel color",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
options.func = options.blend || defaults.blend;
|
||||
options.func = options.func || defaults.blend;
|
||||
options.offset = options.offset || defaults.offset;
|
||||
options.blendMode = options.blendMode || defaults.blendMode;
|
||||
|
||||
var output;
|
||||
|
||||
@@ -16,6 +15,9 @@ module.exports = function Blend(options, UI, util) {
|
||||
|
||||
var step = this;
|
||||
|
||||
// convert to runnable code:
|
||||
if (typeof options.func === 'string') eval('options.func = ' + options.func);
|
||||
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
// convert offset as string to int
|
||||
@@ -30,83 +32,26 @@ module.exports = function Blend(options, UI, util) {
|
||||
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) {
|
||||
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) {
|
||||
// blend!
|
||||
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(
|
||||
r2, g2, b2, a2, r1, g1, b1, a1
|
||||
);
|
||||
else {
|
||||
return blends[options.blendMode]();
|
||||
}
|
||||
|
||||
return options.func(
|
||||
r2, g2, b2, a2,
|
||||
p.get(x, y, 0),
|
||||
p.get(x, y, 1),
|
||||
p.get(x, y, 2),
|
||||
p.get(x, y, 3)
|
||||
);
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
// run PixelManipulatin on second image's pixels
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
{
|
||||
"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": {
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"desc": "Choose which image to blend the current image with. Two steps back is -2, three steps back is -3 etc.",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
canvas.width = pixels.shape[0];
|
||||
canvas.height = pixels.shape[1];
|
||||
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);
|
||||
|
||||
|
||||
@@ -18,26 +25,26 @@ module.exports = function(pixels){
|
||||
let unknown = 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.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);
|
||||
cv.erode(gray, gray, M);
|
||||
cv.dilate(gray, opening, M);
|
||||
cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3);
|
||||
|
||||
// Distance transform
|
||||
// distance transform
|
||||
cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5);
|
||||
cv.normalize(distTrans, distTrans, 1, 0, cv.NORM_INF);
|
||||
|
||||
// Get foreground
|
||||
// get foreground
|
||||
cv.threshold(distTrans, imageFg, 0.7 * 1, 255, cv.THRESH_BINARY);
|
||||
imageFg.convertTo(imageFg, cv.CV_8U, 1, 0);
|
||||
cv.subtract(imageBg, imageFg, unknown);
|
||||
|
||||
// Get connected components markers
|
||||
// get connected components markers
|
||||
cv.connectedComponents(imageFg, markers);
|
||||
for (let i = 0; i < markers.rows; i++) {
|
||||
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.watershed(src, markers);
|
||||
|
||||
// Grow barriers
|
||||
// draw barriers
|
||||
for (let i = 0; i < markers.rows; i++) {
|
||||
for (let j = 0; j < markers.cols; j++) {
|
||||
if (markers.intPtr(i, j)[0] == -1) {
|
||||
src.ucharPtr(i, j)[0] = 255; // Red
|
||||
src.ucharPtr(i, j)[1] = 0; // Green
|
||||
src.ucharPtr(i, j)[2] = 0; // Blue
|
||||
src.ucharPtr(i, j)[0] = 255; // R
|
||||
src.ucharPtr(i, j)[1] = 0; // G
|
||||
src.ucharPtr(i, j)[2] = 0; // B
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,4 +78,4 @@ module.exports = function(pixels){
|
||||
pixels.data = myImageData.data;
|
||||
|
||||
return pixels;
|
||||
};
|
||||
};
|
||||
@@ -10,14 +10,17 @@ module.exports = function BlobAnalysis(options, UI){
|
||||
|
||||
var step = this;
|
||||
|
||||
var priorStep = this.getStep(-1); // get the previous step to process it
|
||||
|
||||
function extraManipulation(pixels){
|
||||
|
||||
pixels = require('./BlobAnalysis')(pixels);
|
||||
pixels = require('./BlobAnalysis')(pixels, options, priorStep);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype){
|
||||
|
||||
step.output = { src: datauri, format: mimetype};
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -37,4 +40,4 @@ module.exports = function BlobAnalysis(options, UI){
|
||||
output: output,
|
||||
UI: UI
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,5 @@
|
||||
"name": "Blob Analysis",
|
||||
"description": "Blob/Region identification for microscopic images.",
|
||||
"inputs": {},
|
||||
"requires": ["webgl", "browser"],
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blob-analysis"
|
||||
}
|
||||
|
||||
@@ -1,36 +1,6 @@
|
||||
// Generates a 5x5 Gaussian kernel
|
||||
function kernelGenerator(sigma = 1) {
|
||||
|
||||
let kernel = [],
|
||||
sum = 0;
|
||||
|
||||
if (sigma == 0) sigma += 0.05;
|
||||
|
||||
const s = 2 * Math.pow(sigma, 2);
|
||||
|
||||
for (let y = -2; y <= 2; y++) {
|
||||
kernel.push([]);
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
let r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
kernel[y + 2].push(Math.exp(-(r / s)));
|
||||
sum += kernel[y + 2][x + 2];
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < 5; x++){
|
||||
for (let y = 0; y < 5; y++){
|
||||
kernel[y][x] = (kernel[y][x] / sum);
|
||||
}
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
module.exports = exports = function(pixels, blur) {
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
let kernel = kernelGenerator(blur), // Generate the Gaussian kernel based on the sigma input.
|
||||
pixs = { // Separates the rgb channel pixels to convolve on the GPU.
|
||||
let kernel = kernelGenerator(blur),
|
||||
pixs = {
|
||||
r: [],
|
||||
g: [],
|
||||
b: [],
|
||||
@@ -48,18 +18,45 @@ module.exports = exports = function(pixels, blur) {
|
||||
}
|
||||
}
|
||||
|
||||
const convolve = require('../_nomodule/gpuUtils').convolve; // GPU convolution function.
|
||||
const convolve = require('../_nomodule/gpuUtils').convolve;
|
||||
|
||||
const conPix = convolve([pixs.r, pixs.g, pixs.b], kernel); // Convolves the pixels (all channels separately) on the GPU.
|
||||
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); // Sets the image pixels according to the blurred values.
|
||||
pixels.set(x, y, 0, Math.max(0, Math.min(conPix[0][y][x], 255)));
|
||||
pixels.set(x, y, 1, Math.max(0, Math.min(conPix[1][y][x], 255)));
|
||||
pixels.set(x, y, 2, Math.max(0, Math.min(conPix[2][y][x], 255)));
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
};
|
||||
|
||||
//Generates a 5x5 Gaussian kernel
|
||||
function kernelGenerator(sigma = 1) {
|
||||
|
||||
let kernel = [],
|
||||
sum = 0;
|
||||
|
||||
if (sigma == 0) sigma += 0.05;
|
||||
|
||||
const s = 2 * Math.pow(sigma, 2);
|
||||
|
||||
for (let y = -2; y <= 2; y++) {
|
||||
kernel.push([]);
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
let r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
kernel[y + 2].push(Math.exp(-(r / s)));
|
||||
sum += kernel[y + 2][x + 2];
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < 5; x++){
|
||||
for (let y = 0; y < 5; y++){
|
||||
kernel[y][x] = (kernel[y][x] / sum);
|
||||
}
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,14 +20,16 @@ module.exports = function Blur(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
inBrowser: options.inBrowser,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
|
||||
@@ -31,8 +31,11 @@ module.exports = function Brightness(options, UI) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
module.exports = function canvasResize(options, UI) {
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
var output;
|
||||
|
||||
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
options.width = parseInt(options.width || defaults.width);
|
||||
@@ -21,22 +21,26 @@ module.exports = function canvasResize(options, UI) {
|
||||
var step = this;
|
||||
|
||||
function extraManipulation(pixels) {
|
||||
|
||||
let newPixels = require('ndarray')(new Uint8Array(4 * options.width * options.height).fill(0), [options.width, options.height, 4]);
|
||||
let iMax = options.width - options.x,
|
||||
jMax = options.height - options.y;
|
||||
for (let i = 0; i < iMax && i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < jMax && j < pixels.shape[1]; j++) {
|
||||
let x = i + options.x, y = j + options.y;
|
||||
pixelSetter(x, y, [pixels.get(i, j, 0), pixels.get(i, j, 1), pixels.get(i, j, 2), pixels.get(i, j, 3)], newPixels);
|
||||
|
||||
newPixels.set(x, y, 0, pixels.get(i, j, 0));
|
||||
newPixels.set(x, y, 1, pixels.get(i, j, 1));
|
||||
newPixels.set(x, y, 2, pixels.get(i, j, 2));
|
||||
newPixels.set(x, y, 3, pixels.get(i, j, 3));
|
||||
}
|
||||
}
|
||||
return newPixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -22,6 +22,5 @@
|
||||
"desc": "Y-cord of the top left corner of the image on the canvas",
|
||||
"default": 500
|
||||
}
|
||||
},
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#canvas-resize-module"
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,11 @@ module.exports = function Channel(options, UI) {
|
||||
if (options.channel === 'blue') return [0, 0, b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
module.exports = function ColorTemperature(options, UI) {
|
||||
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
|
||||
var output;
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
options.temperature = options.temperature || defaults.temperature;
|
||||
options.temperature = (options.temperature > 40000) ? 40000 : options.temperature;
|
||||
options.temperature = (options.temperature < 0) ? 0 : options.temperature;
|
||||
options.temperature = (options.temperature > '40000') ? '40000' : options.temperature;
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
@@ -43,20 +38,27 @@ module.exports = function ColorTemperature(options, UI) {
|
||||
for (let i = 0; i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < pixels.shape[1]; j++) {
|
||||
|
||||
var rgbdata = [pixels.get(i, j, 0), pixels.get(i, j, 1), pixels.get(i, j, 2)];
|
||||
rgbdata[0] = (255 / r) * rgbdata[0];
|
||||
rgbdata[1] = (255 / g) * rgbdata[1];
|
||||
rgbdata[2] = (255 / b) * rgbdata[2];
|
||||
pixelSetter(i, j, rgbdata, pixels);
|
||||
r_data = pixels.get(i, j, 0);
|
||||
r_new_data = (255 / r) * r_data;
|
||||
pixels.set(i, j, 0, r_new_data);
|
||||
|
||||
g_data = pixels.get(i, j, 1);
|
||||
g_new_data = (255 / g) * g_data;
|
||||
pixels.set(i, j, 1, g_new_data);
|
||||
|
||||
b_data = pixels.get(i, j, 2);
|
||||
b_new_data = (255 / b) * b_data;
|
||||
pixels.set(i, j, 2, b_new_data);
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
module.exports = require('../../util/createMetaModule.js')(
|
||||
function mapFunction(options) {
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
|
||||
// return steps with options:
|
||||
return [
|
||||
{ 'name': 'gradient', 'options': {} },
|
||||
{ 'name': 'colormap', 'options': { colormap: options.colormap || defaults.colormap } },
|
||||
{ 'name': 'crop', 'options': { 'y': 0, 'w': '100%', 'h': options.h || defaults.h } },
|
||||
{ 'name': 'overlay', 'options': { 'x': options.x || defaults.h, 'y': options.y || defaults.y, 'offset': -4 } }
|
||||
{ 'name': 'colormap', 'options': { colormap: options.colormap } },
|
||||
{ 'name': 'crop', 'options': { 'y': 0, 'h': options.h } },
|
||||
{ 'name': 'overlay', 'options': { 'x': options.x, 'y': options.y, 'offset': -4 } }
|
||||
];
|
||||
}, {
|
||||
infoJson: require('./info.json')
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
*/
|
||||
|
||||
module.exports = function Colormap(value, options) {
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.colormap = options.colormap || defaults.colormap;
|
||||
options.colormap = options.colormap || colormaps.default;
|
||||
// if a lookup table is provided as an array:
|
||||
if(typeof(options.colormap) == 'object')
|
||||
colormapFunction = colormap(options.colormap);
|
||||
|
||||
@@ -16,10 +16,12 @@ module.exports = function Colormap(options, UI) {
|
||||
return [res[0], res[1], res[2], 255];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
];
|
||||
@@ -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":""
|
||||
}
|
||||
|
||||
47
src/modules/Contrast/Contrast.js
Normal file
47
src/modules/Contrast/Contrast.js
Normal file
@@ -0,0 +1,47 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
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 r = oldpix.get(i, j, 0) / 255.0;
|
||||
r -= 0.5;
|
||||
r *= contrast;
|
||||
r += 0.5;
|
||||
r *= 255;
|
||||
if (r < 0) r = 0;
|
||||
if (r > 255) r = 255;
|
||||
|
||||
|
||||
var g = oldpix.get(i, j, 1) / 255.0;
|
||||
g -= 0.5;
|
||||
g *= contrast;
|
||||
g += 0.5;
|
||||
g *= 255;
|
||||
if (g < 0) g = 0;
|
||||
if (g > 255) g = 255;
|
||||
|
||||
|
||||
var b = oldpix.get(i, j, 2) / 255.0;
|
||||
b -= 0.5;
|
||||
b *= contrast;
|
||||
b += 0.5;
|
||||
b *= 255;
|
||||
if (b < 0) b = 0;
|
||||
if (b > 255) b = 255;
|
||||
|
||||
|
||||
pixels.set(i, j, 0, r);
|
||||
pixels.set(i, j, 1, g);
|
||||
pixels.set(i, j, 2, b);
|
||||
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
};
|
||||
@@ -15,41 +15,25 @@ module.exports = function Contrast(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
let contrast = options.contrast;
|
||||
|
||||
contrast = Number(contrast);
|
||||
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 extraManipulation(pixels) {
|
||||
pixels = require('./Contrast')(pixels, options.contrast);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
|
||||
return [changeContrast(r / 255), changeContrast(g / 255), changeContrast(b / 255), a];
|
||||
}
|
||||
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 };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback,
|
||||
inBrowser: options.inBrowser,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
var _ = require('lodash');
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
module.exports = exports = function(pixels, constantFactor, kernelValues, texMode) {
|
||||
let kernel = kernelGenerator(constantFactor, kernelValues),
|
||||
pixs = {
|
||||
@@ -26,8 +24,9 @@ module.exports = exports = function(pixels, constantFactor, kernelValues, texMod
|
||||
|
||||
for (let y = 0; y < pixels.shape[1]; y++){
|
||||
for (let x = 0; x < pixels.shape[0]; x++){
|
||||
var value = [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, value, pixels);
|
||||
pixels.set(x, y, 0, Math.max(0, Math.min(conPix[0][y][x], 255)));
|
||||
pixels.set(x, y, 1, Math.max(0, Math.min(conPix[1][y][x], 255)));
|
||||
pixels.set(x, y, 2, Math.max(0, Math.min(conPix[2][y][x], 255)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ module.exports = function Convolution(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -29,7 +31,6 @@ module.exports = function Convolution(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -1,71 +1,61 @@
|
||||
const ndarray = require('ndarray'),
|
||||
pixelSetter = require('../../util/pixelSetter'),
|
||||
parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
|
||||
|
||||
module.exports = function Crop(pixels, options, cb) {
|
||||
module.exports = function Crop(input, options, callback) {
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.x = options.x || defaults.x;
|
||||
options.y = options.y || defaults.y;
|
||||
var getPixels = require('get-pixels'),
|
||||
savePixels = require('save-pixels');
|
||||
|
||||
options.w = options.w || defaults.w;
|
||||
options.h = options.h || defaults.h;
|
||||
options.x = parseInt(options.x) || defaults.x;
|
||||
options.y = parseInt(options.y) || defaults.y;
|
||||
|
||||
options.backgroundColor = options.backgroundColor || defaults.backgroundColor;
|
||||
getPixels(input.src, function(err, pixels){
|
||||
options.w = parseInt(options.w) || Math.floor(pixels.shape[0]);
|
||||
options.h = parseInt(options.h) || Math.floor(pixels.shape[1]);
|
||||
options.backgroundColor = options.backgroundColor || defaults.backgroundColor;
|
||||
var ox = options.x;
|
||||
var oy = options.y;
|
||||
var w = options.w;
|
||||
var h = options.h;
|
||||
var iw = pixels.shape[0]; //Width of Original Image
|
||||
var ih = pixels.shape[1]; //Height of Original Image
|
||||
var backgroundArray = [];
|
||||
backgroundColor = options.backgroundColor.substring(options.backgroundColor.indexOf('(') + 1, options.backgroundColor.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
backgroundColor = backgroundColor.split(',');
|
||||
for(var i = 0; i < w ; i++){
|
||||
backgroundArray = backgroundArray.concat([backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]]);
|
||||
}
|
||||
// var newarray = new Uint8Array(4*w*h);
|
||||
var array = [];
|
||||
for (var n = oy; n < oy + h; n++) {
|
||||
var offsetValue = 4 * w * n;
|
||||
if(n < ih){
|
||||
var start = n * 4 * iw + ox * 4;
|
||||
var end = n * 4 * iw + ox * 4 + 4 * w;
|
||||
var pushArray = Array.from(pixels.data.slice(start, end ));
|
||||
array.push.apply(array, pushArray);
|
||||
} else {
|
||||
array.push.apply(array, backgroundArray);
|
||||
}
|
||||
}
|
||||
|
||||
var newarray = Uint8Array.from(array);
|
||||
pixels.data = newarray;
|
||||
pixels.shape = [w, h, 4];
|
||||
pixels.stride[1] = 4 * w;
|
||||
|
||||
const bg = options.backgroundColor.replace('rgba', '').replace('(', '').replace(')', '').split(',');
|
||||
options.format = input.format;
|
||||
|
||||
let iw = pixels.shape[0], // Width of Original Image
|
||||
ih = pixels.shape[1], // Height of Original Image
|
||||
offsetX,
|
||||
offsetY,
|
||||
w,
|
||||
h;
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format);
|
||||
|
||||
// Parse the inputs
|
||||
parseCornerCoordinateInputs({iw, ih},
|
||||
{
|
||||
x: { valInp: options.x, type: 'horizontal' },
|
||||
y: { valInp: options.y, type: 'vertical' },
|
||||
w: { valInp: options.w, type: 'horizontal' },
|
||||
h: { valInp: options.h, type: 'vertical' },
|
||||
}, function (opt, coord) {
|
||||
offsetX = Math.floor(coord.x.valInp);
|
||||
offsetY = Math.floor(coord.y.valInp);
|
||||
w = Math.floor(coord.w.valInp);
|
||||
h = Math.floor(coord.h.valInp);
|
||||
r.on('data', function(chunk){
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
const newPixels = new ndarray([], [w, h, 4]);
|
||||
|
||||
for (let x = 0; x < w; x++) {
|
||||
for (let y = 0; y < h; y++) {
|
||||
pixelSetter(x, y, bg, newPixels); // Set the background color
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
r.on('end', function(){
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
callback(datauri, options.format);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const pixelManipulation = require('../_nomodule/PixelManipulation');
|
||||
/*
|
||||
* Image Cropping module
|
||||
* Usage:
|
||||
@@ -27,37 +26,54 @@ module.exports = function CropModule(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
// save the input image;
|
||||
// TODO: this should be moved to module API to persist the input image
|
||||
options.step.input = input.src;
|
||||
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
|
||||
|
||||
function extraManipulation(pixels) {
|
||||
const newPixels = require('./Crop')(pixels, options, function() {
|
||||
// We should do this via event/listener:
|
||||
if (ui && ui.hide) ui.hide();
|
||||
|
||||
// Start custom UI setup (draggable UI)
|
||||
// Only once we have an input image
|
||||
if (setupComplete === false && options.step.inBrowser && !options.noUI) {
|
||||
setupComplete = true;
|
||||
ui.setup();
|
||||
}
|
||||
});
|
||||
return newPixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
|
||||
return pixelManipulation(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
//parse the inputs
|
||||
parseCornerCoordinateInputs(options, {
|
||||
src: input.src,
|
||||
x: { valInp: options.x, type: 'horizontal' },
|
||||
y: { valInp: options.y, type: 'vertical' },
|
||||
w: { valInp: options.w, type: 'horizontal' },
|
||||
h: { valInp: options.h, type: 'vertical' },
|
||||
}, function (options, coord) {
|
||||
options.x = parseInt(coord.x.valInp);
|
||||
options.y = parseInt(coord.y.valInp);
|
||||
options.w = coord.w.valInp;
|
||||
options.h = coord.h.valInp;
|
||||
});
|
||||
|
||||
require('./Crop')(input, options, function (out, format) {
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: out,
|
||||
format: format
|
||||
};
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = out;
|
||||
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
|
||||
// we should do this via event/listener:
|
||||
if (ui && ui.hide) ui.hide();
|
||||
|
||||
// start custom UI setup (draggable UI)
|
||||
// only once we have an input image
|
||||
if (setupComplete === false && options.step.inBrowser && !options.noUI) {
|
||||
setupComplete = true;
|
||||
ui.setup();
|
||||
}
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -59,6 +59,12 @@ module.exports = function CropModuleUi(step, ui) {
|
||||
];
|
||||
}
|
||||
|
||||
function remove() {
|
||||
$(imgEl()).imgAreaSelect({
|
||||
remove: true
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
// then hide the draggable UI
|
||||
$(imgEl()).imgAreaSelect({
|
||||
@@ -86,6 +92,7 @@ module.exports = function CropModuleUi(step, ui) {
|
||||
|
||||
return {
|
||||
setup: setup,
|
||||
remove: remove,
|
||||
hide: hide
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
"w": {
|
||||
"type": "string",
|
||||
"desc": "Width of crop",
|
||||
"default": "100%"
|
||||
"default": "(50%)"
|
||||
},
|
||||
"h": {
|
||||
"type": "string",
|
||||
"desc": "Height of crop",
|
||||
"default": "100%"
|
||||
"default": "(50%)"
|
||||
},
|
||||
"backgroundColor": {
|
||||
"type": "text",
|
||||
|
||||
@@ -28,16 +28,18 @@ module.exports = function DoNothing(options, UI) {
|
||||
options.step.qrval = (decoded) ? decoded.data : 'undefined';
|
||||
});
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {
|
||||
src: datauri,
|
||||
format: mimetype
|
||||
};
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -15,8 +15,10 @@ module.exports = function Dither(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -25,7 +27,6 @@ module.exports = function Dither(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
module.exports = exports = function(pixels, options){
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
options.startingX = options.startingX || defaults.startingX;
|
||||
options.startingY = options.startingY || defaults.startingY;
|
||||
|
||||
var ox = Number(options.startingX),
|
||||
oy = Number(options.startingY),
|
||||
iw = pixels.shape[0],
|
||||
ih = pixels.shape[1],
|
||||
thickness = Number(options.thickness) || defaults.thickness,
|
||||
ex = Number(options.endX || defaults.endX) - thickness || iw - 1,
|
||||
ey = Number(options.endY || defaults.endY) - thickness || ih - 1,
|
||||
ex = options.endX = Number(options.endX) - thickness || iw - 1,
|
||||
ey = options.endY = Number(options.endY) - thickness || ih - 1,
|
||||
color = options.color || defaults.color;
|
||||
|
||||
color = color.substring(color.indexOf('(') + 1, color.length - 1); // Extract only the values from rgba(_,_,_,_)
|
||||
color = color.substring(color.indexOf('(') + 1, color.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
color = color.split(',');
|
||||
|
||||
var drawSide = function(startX, startY, endX, endY){
|
||||
for (var n = startX; n <= endX + thickness; n++){
|
||||
for (var k = startY; k <= endY + thickness; k++){
|
||||
|
||||
pixelSetter(n, k, [color[0], color[1], color[2]], pixels); // To remove 4th channel - pixels.set(n, k, 3, color[3]);
|
||||
pixels.set(n, k, 0, color[0]);
|
||||
pixels.set(n, k, 1, color[1]);
|
||||
pixels.set(n, k, 2, color[2]);
|
||||
//pixels.set(n, k, 3, color[3]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,8 +19,10 @@ module.exports = function DrawRectangle(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -30,7 +32,6 @@ module.exports = function DrawRectangle(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
"endX":{
|
||||
"type": "integer",
|
||||
"desc": "last x position of the rectangle",
|
||||
"default": 10
|
||||
"default": "width"
|
||||
},
|
||||
|
||||
"endY":{
|
||||
"type": "integer",
|
||||
"desc": "last y position of the rectangle",
|
||||
"default": 10
|
||||
"default": "height"
|
||||
},
|
||||
|
||||
"thickness":{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = function Dynamic(options, UI) {
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
@@ -10,12 +10,13 @@ module.exports = function Dynamic(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
options.red = options.red || defaults.red;
|
||||
options.blue = options.blue || defaults.blue;
|
||||
options.green = options.green || defaults.green;
|
||||
// start with monochrome, but if options.red, options.green, and options.blue are set, accept them too
|
||||
options.monochrome = options.monochrome || '(R+G+B)/3';
|
||||
|
||||
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;
|
||||
eval(func);
|
||||
return f;
|
||||
@@ -24,12 +25,9 @@ module.exports = function Dynamic(options, UI) {
|
||||
var channels = ['red', 'green', 'blue', 'alpha'];
|
||||
|
||||
channels.forEach(function(channel) {
|
||||
if (channel === 'alpha'){
|
||||
options['alpha_function'] = function() { return 255; };
|
||||
}
|
||||
else{
|
||||
options[channel + '_function'] = generator(options[channel]);
|
||||
}
|
||||
if (options.hasOwnProperty(channel)) options[channel + '_function'] = generator(options[channel]);
|
||||
else if (channel === 'alpha') options['alpha_function'] = function() { return 255; };
|
||||
else options[channel + '_function'] = generator(options.monochrome);
|
||||
});
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -73,10 +71,12 @@ module.exports = function Dynamic(options, UI) {
|
||||
}
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"type": "string",
|
||||
"desc": "Expression to return for blue channel with R, G, B, and A inputs",
|
||||
"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"
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// Read More: https://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
// Define kernels for the sobel filter.
|
||||
// Define kernels for the sobel filter
|
||||
const kernelx = [
|
||||
[-1, 0, 1],
|
||||
[-2, 0, 2],
|
||||
@@ -23,7 +19,7 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
|
||||
grads.push([]);
|
||||
angles.push([]);
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
var result = sobelFilter( // Convolves the sobel filter on every pixel
|
||||
var result = sobelFilter(
|
||||
pixels,
|
||||
x,
|
||||
y
|
||||
@@ -34,47 +30,33 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
|
||||
angles.slice(-1)[0].push(result.angle);
|
||||
}
|
||||
}
|
||||
nonMaxSupress(pixels, grads, angles); // Non Maximum Suppression: Filter fine edges.
|
||||
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels); // Double Threshold: Categorizes edges into strong and weak edges based on two thresholds.
|
||||
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels); // Optional Hysteresis (very slow) to minimize edges generated due to noise.
|
||||
nonMaxSupress(pixels, grads, angles);
|
||||
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels);
|
||||
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels);
|
||||
|
||||
strongEdgePixels.forEach(pixel => preserve(pixels, pixel)); // Makes the strong edges White.
|
||||
weakEdgePixels.forEach(pixel => supress(pixels, pixel)); // Makes the weak edges black(bg color) after filtering.
|
||||
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel)); // Makes the rest of the image black.
|
||||
strongEdgePixels.forEach(pixel => preserve(pixels, pixel));
|
||||
weakEdgePixels.forEach(pixel => supress(pixels, pixel));
|
||||
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel));
|
||||
|
||||
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) {
|
||||
pixelSetter(pixel[0], pixel[1], [0, 0, 0, 255], pixels);
|
||||
pixels.set(pixel[0], pixel[1], 0, 0);
|
||||
pixels.set(pixel[0], pixel[1], 1, 0);
|
||||
pixels.set(pixel[0], pixel[1], 2, 0);
|
||||
pixels.set(pixel[0], pixel[1], 3, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
pixelSetter(pixel[0], pixel[1], [255, 255, 255, 255], pixels);
|
||||
pixels.set(pixel[0], pixel[1], 0, 255);
|
||||
pixels.set(pixel[0], pixel[1], 1, 255);
|
||||
pixels.set(pixel[0], pixel[1], 2, 255);
|
||||
pixels.set(pixel[0], pixel[1], 3, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
// sobelFilter function that convolves sobel kernel over every pixel
|
||||
function sobelFilter(pixels, x, y) {
|
||||
let val = pixels.get(x, y, 0),
|
||||
gradX = 0.0,
|
||||
@@ -86,8 +68,8 @@ function sobelFilter(pixels, x, y) {
|
||||
let xn = x + a - 1,
|
||||
yn = y + b - 1;
|
||||
|
||||
if (isOutOfBounds(pixels, xn, yn)) { // Fallback for coordinates which lie outside the image.
|
||||
gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b]; // Fallback to nearest pixel
|
||||
if (isOutOfBounds(pixels, xn, yn)) {
|
||||
gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b];
|
||||
gradY += pixels.get(xn + 1, yn + 1, 0) * kernely[a][b];
|
||||
}
|
||||
else {
|
||||
@@ -105,12 +87,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){
|
||||
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;
|
||||
@@ -125,25 +101,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){
|
||||
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 arrelem !== elem;
|
||||
});
|
||||
};
|
||||
|
||||
// Non Maximum Supression without interpolation.
|
||||
// Non Maximum Supression without interpolation
|
||||
function nonMaxSupress(pixels, grads, angles) {
|
||||
angles = angles.map((arr) => arr.map(convertToDegrees));
|
||||
|
||||
@@ -153,7 +121,7 @@ function nonMaxSupress(pixels, grads, angles) {
|
||||
let angleCategory = categorizeAngle(angles[x][y]);
|
||||
|
||||
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:
|
||||
if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) {
|
||||
pixelsToBeSupressed.push([x, y]);
|
||||
@@ -182,24 +150,17 @@ function nonMaxSupress(pixels, grads, angles) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @method convertToDegrees
|
||||
* @description Converts the given angle(in radians) to degrees.
|
||||
* @param {Number} radians Angle in radians
|
||||
* @returns {Number} Angle in degrees
|
||||
*/
|
||||
// Converts radians to degrees
|
||||
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)));
|
||||
|
||||
// Applies the double threshold to the image.
|
||||
// Applies the double threshold to the image
|
||||
function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) {
|
||||
|
||||
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio, // High Threshold relative to the strongest edge
|
||||
lowThreshold = highThreshold * lowThresholdRatio; // Low threshold relative to high threshold
|
||||
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio,
|
||||
lowThreshold = highThreshold * lowThresholdRatio;
|
||||
|
||||
for (let x = 0; x < pixels.shape[0]; x++) {
|
||||
for (let y = 0; y < pixels.shape[1]; y++) {
|
||||
@@ -220,12 +181,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){
|
||||
strongEdgePixels.forEach(pixel => {
|
||||
let x = pixel[0],
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
const Blur = require('../Blur/Blur');
|
||||
/*
|
||||
* Detect Edges in an Image
|
||||
* Uses Canny method for the same
|
||||
* Read more: https://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
*/
|
||||
module.exports = function edgeDetect(options, UI) {
|
||||
|
||||
@@ -22,31 +19,42 @@ module.exports = function edgeDetect(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
// Makes the image greyscale
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
|
||||
}
|
||||
// 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 extraManipulation(pixels) {
|
||||
const blurPixels = Blur(pixels, options.blur);
|
||||
return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
|
||||
}
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
function extraManipulation() {
|
||||
return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm: options.useWasm
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm: options.useWasm
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,4 +64,4 @@ module.exports = function edgeDetect(options, UI) {
|
||||
output: output,
|
||||
UI: UI
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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": {
|
||||
"blur": {
|
||||
"type": "float",
|
||||
|
||||
@@ -24,8 +24,11 @@ module.exports = function Exposure(options, UI) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
const _ = require('lodash');
|
||||
/*
|
||||
* Flip the image on vertical/horizontal axis.
|
||||
*/
|
||||
module.exports = function FlipImage(options, UI) {
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.Axis = options.Axis || defaults.Axis;
|
||||
options.Axis = options.Axis || require('./info.json').inputs.Axis.default;
|
||||
|
||||
let output;
|
||||
var output,
|
||||
getPixels = require('get-pixels');
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
@@ -15,30 +14,34 @@ module.exports = function FlipImage(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
function extraManipulation(pixels) {
|
||||
const oldPixels = _.cloneDeep(pixels);
|
||||
return getPixels(input.src, function(err, oldPixels) {
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
function extraManipulation(pixels) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
return require('./flipImage')(oldPixels, pixels, options.Axis);
|
||||
}
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('./flipImage')(oldPixels, pixels, options.Axis);
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
ui: options.step.ui,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
module.exports = function flipImage(oldPixels, pixels, axis) {
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
var width = oldPixels.shape[0],
|
||||
height = oldPixels.shape[1];
|
||||
|
||||
function copyPixel(x1, y1, x2, y2){
|
||||
pixelSetter(x1, y1, [oldPixels.get(x2, y2, 0), oldPixels.get(x2, y2, 1), oldPixels.get(x2, y2, 2), oldPixels.get(x2, y2, 3)], pixels);
|
||||
|
||||
pixels.set(x1, y1, 0, oldPixels.get(x2, y2, 0));
|
||||
pixels.set(x1, y1, 1, oldPixels.get(x2, y2, 1));
|
||||
pixels.set(x1, y1, 2, oldPixels.get(x2, y2, 2));
|
||||
pixels.set(x1, y1, 3, oldPixels.get(x2, y2, 3));
|
||||
}
|
||||
|
||||
function flip(){
|
||||
|
||||
@@ -10,7 +10,8 @@ module.exports = function Gamma(options, UI) {
|
||||
var step = this;
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json')),
|
||||
val = options.adjustment || defaults.adjustment;
|
||||
adjustment = options.adjustment || defaults.adjustment;
|
||||
var val = adjustment / defaults.adjustment;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
|
||||
@@ -21,8 +22,10 @@ module.exports = function Gamma(options, UI) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
"adjustment": {
|
||||
"type": "float",
|
||||
"desc": "gamma correction (inverse of actual gamma factor) for the new image",
|
||||
"default": 0.5,
|
||||
"min": 0,
|
||||
"max": 5,
|
||||
"step": 0.1
|
||||
"default": 0.2,
|
||||
"min": 2,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#gamma-correction-module"
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
const pixelSetter = require('../../util/pixelSetter.js'),
|
||||
pixelManipulation = require('../_nomodule/PixelManipulation');
|
||||
|
||||
module.exports = function Gradient(options, UI) {
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.gradientType = options.gradientType || defaults.gradientType;
|
||||
module.exports = function Invert(options, UI) {
|
||||
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input, callback) {
|
||||
function draw(input, callback, progressObj) {
|
||||
var getPixels = require('get-pixels');
|
||||
var savePixels = require('save-pixels');
|
||||
|
||||
var step = this;
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
getPixels(input.src, function(err, pixels) {
|
||||
|
||||
function extraManipulation(pixels) {
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
console.log('Bad Image path');
|
||||
return;
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
var width = pixels.shape[0];
|
||||
|
||||
return pixelManipulation(input, {
|
||||
output,
|
||||
extraManipulation,
|
||||
callback,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
useWasm:options.useWasm
|
||||
for (var i = 0; i < pixels.shape[0]; i++) {
|
||||
for (var j = 0; j < pixels.shape[1]; j++) {
|
||||
let val = (i / width) * 255;
|
||||
pixels.set(i, j, 0, val);
|
||||
pixels.set(i, j, 1, val);
|
||||
pixels.set(i, j, 2, val);
|
||||
pixels.set(i, j, 3, 255);
|
||||
}
|
||||
}
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, input.format, { quality: 100 });
|
||||
|
||||
r.on('data', function(chunk) {
|
||||
totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
r.on('end', function() {
|
||||
var data = Buffer.concat(chunks, totalLength).toString('base64');
|
||||
var datauri = 'data:image/' + input.format + ';base64,' + data;
|
||||
output(input.image, datauri, input.format);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
{
|
||||
"name": "gradient",
|
||||
"description": "Gives a gradient of the image",
|
||||
"inputs": {
|
||||
"gradientType": {
|
||||
"type": "select",
|
||||
"desc": "Choose between linear or circular gradient",
|
||||
"default": "linear",
|
||||
"values": ["linear", "circular"]
|
||||
}
|
||||
},
|
||||
"inputs": {},
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#gradient-module"
|
||||
}
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
module.exports = exports = function(pixels, options){
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
if(Number(options.x) == 0){
|
||||
options.x = 1;
|
||||
}
|
||||
if( Number(options.y) == 0) {
|
||||
options.y = 1;
|
||||
}
|
||||
|
||||
options.x = Math.abs(Number(options.x)) || defaults.x;
|
||||
options.y = Math.abs(Number(options.y)) || defaults.y;
|
||||
options.x = Number(options.x) || defaults.x;
|
||||
options.y = Number(options.y) || defaults.y;
|
||||
color = options.color || defaults.color;
|
||||
color = color.substring(color.indexOf('(') + 1, color.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
color = color.split(',');
|
||||
|
||||
for(var x = 0; x < pixels.shape[0]; x += options.x){
|
||||
for(var y = 0 ; y < pixels.shape[1]; y++){
|
||||
pixelSetter(x, y, [color[0], color[1], color[2]], pixels); // to remove 4th channel - pixels.set(x, y, 3, color[3]);
|
||||
|
||||
pixels.set(x, y, 0, color[0]);
|
||||
pixels.set(x, y, 1, color[1]);
|
||||
pixels.set(x, y, 2, color[2]);
|
||||
//pixels.set(x, y, 3, color[3]);
|
||||
}
|
||||
}
|
||||
|
||||
for(var y = 0; y < pixels.shape[1]; y += options.y){
|
||||
for(var x = 0 ; x < pixels.shape[0]; x++){
|
||||
pixelSetter(x, y, [color[0], color[1], color[2]], pixels); // to remove 4th channel - pixels.set(x, y, 3, color[3]);
|
||||
|
||||
pixels.set(x, y, 0, color[0]);
|
||||
pixels.set(x, y, 1, color[1]);
|
||||
pixels.set(x, y, 2, color[2]);
|
||||
//pixels.set(x, y, 3, color[3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@ module.exports = function GridOverlay(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -7,11 +7,9 @@ module.exports = function Channel(options, UI) {
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
const defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.gradient = options.gradient || defaults.gradient;
|
||||
options.gradient = String(JSON.parse(options.gradient));
|
||||
options.gradient = JSON.parse(options.gradient);
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
@@ -34,8 +32,10 @@ module.exports = function Channel(options, UI) {
|
||||
|
||||
for (let x = 0; x < 256; x++) {
|
||||
for (let y = 0; y < 256; y++) {
|
||||
pixelSetter(x, y, [255, 255, 255, 255], pixels);
|
||||
|
||||
pixels.set(x, y, 0, 255);
|
||||
pixels.set(x, y, 1, 255);
|
||||
pixels.set(x, y, 2, 255);
|
||||
pixels.set(x, y, 3, 255);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ module.exports = function Channel(options, UI) {
|
||||
if (options.gradient) {
|
||||
for (let x = 0; x < 256; x++) {
|
||||
for (let y = 0; y < 10; y++) {
|
||||
pixelSetter(x, 255 - y, [x, x, x], pixels);
|
||||
|
||||
pixels.set(x, 255 - y, 0, x);
|
||||
pixels.set(x, 255 - y, 1, x);
|
||||
pixels.set(x, 255 - y, 2, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,16 +56,20 @@ module.exports = function Channel(options, UI) {
|
||||
let pixCount = Math.round(convfactor * hist[x]);
|
||||
|
||||
for (let y = startY; y < pixCount; y++) {
|
||||
pixelSetter(x, 255 - y, [204, 255, 153], pixels);
|
||||
|
||||
pixels.set(x, 255 - y, 0, 204);
|
||||
pixels.set(x, 255 - y, 1, 255);
|
||||
pixels.set(x, 255 - y, 2, 153);
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = function ImportImageModuleUi(step, ui) {
|
||||
|
||||
// add a file input listener
|
||||
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>\
|
||||
<i>Select or drag in an image to overlay.</i>\
|
||||
</p>\
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const pixelManipulation = require('../_nomodule/PixelManipulation');
|
||||
/*
|
||||
* Invert the image
|
||||
*/
|
||||
@@ -18,11 +17,14 @@ function Invert(options, UI) {
|
||||
return [255 - r, 255 - g, 255 - b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return pixelManipulation(input, {
|
||||
return input.pixelManipulation({
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
@@ -41,4 +43,10 @@ function Invert(options, UI) {
|
||||
UI: UI
|
||||
};
|
||||
}
|
||||
module.exports = Invert;
|
||||
var info = {
|
||||
'name': 'invert',
|
||||
'description': 'Inverts the image.',
|
||||
'inputs': {
|
||||
}
|
||||
};
|
||||
module.exports = [Invert, info];
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
];
|
||||
69
src/modules/Matcher/Module.js
Normal file
69
src/modules/Matcher/Module.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Match the images
|
||||
*/
|
||||
function Match(options, UI) {
|
||||
|
||||
var output, points;
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
|
||||
global.XY = [
|
||||
options.imageX || defaults.imageX,
|
||||
options.imageY || defaults.imageY
|
||||
];
|
||||
|
||||
const res = require('matcher-core');
|
||||
|
||||
async function run() {
|
||||
const r = await res;
|
||||
return r;
|
||||
}
|
||||
run().then(function(r) {
|
||||
points = r.points;
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a, x, y) {
|
||||
for(var i = 0; i < 500; i++){
|
||||
if(Math.abs(points[i].x - x) <= 10 && Math.abs(points[i].y - y) <= 10){
|
||||
return [0, 255, 0];
|
||||
}
|
||||
}
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return input.pixelManipulation({
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Match;
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
require('./Module'),
|
||||
require('./info.json')
|
||||
];
|
||||
];
|
||||
15
src/modules/Matcher/info.json
Normal file
15
src/modules/Matcher/info.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "matcher-core",
|
||||
"description": "Pattern-mining module for detecting key-points in images",
|
||||
"url": "https://github.com/publiclab/matcher-core.git",
|
||||
"inputs": {
|
||||
"imageX": {
|
||||
"type": "text",
|
||||
"default": ""
|
||||
},
|
||||
"imageY": {
|
||||
"type": "text",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ module.exports = function MinifyImage(options, UI) {
|
||||
reader.readAsDataURL(result);
|
||||
reader.onloadend = function () {
|
||||
base64data = reader.result;
|
||||
output(null, base64data, input.format, false);
|
||||
output(base64data, input.format);
|
||||
if (callback) callback();
|
||||
return;
|
||||
};
|
||||
@@ -76,14 +76,19 @@ module.exports = function MinifyImage(options, UI) {
|
||||
});
|
||||
var destPath = __dirname + '/results/test.' + input.format;
|
||||
var data = base64Img.base64Sync(destPath);
|
||||
output(null, data, input.format, false);
|
||||
output(data, input.format);
|
||||
if (callback) callback();
|
||||
})().catch(e => console.log(e));
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {
|
||||
src: datauri,
|
||||
format: mimetype
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 600 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 222 KiB |
@@ -25,8 +25,11 @@ module.exports = function Ndvi(options, UI) {
|
||||
return [x, x, x, a];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
function modifiedCallback() {
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
{
|
||||
"name": "ndvi-colormap",
|
||||
"description": "Sequentially Applies NDVI and Colormap steps",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
},
|
||||
"inputs": {},
|
||||
"docs-link": "https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#ndvi-colormap-module"
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ module.exports = function NoiseReduction(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -25,7 +27,6 @@ module.exports = function NoiseReduction(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -4,17 +4,12 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
options.x = options.x || defaults.x;
|
||||
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;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
options.offset = parseInt(options.offset || defaults.offset);
|
||||
options.offset = parseInt(options.offset) || -2;
|
||||
|
||||
progressObj.stop(true);
|
||||
progressObj.overrideFlag = true;
|
||||
@@ -23,6 +18,16 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
|
||||
var parseCornerCoordinateInputs = require('../../util/ParseInputCoordinates');
|
||||
|
||||
//parse the inputs
|
||||
parseCornerCoordinateInputs(options, {
|
||||
src: input.src,
|
||||
x: { valInp: options.x, type: 'horizontal' },
|
||||
y: { valInp: options.y, type: 'vertical' },
|
||||
}, function(options, input) {
|
||||
options.x = parseInt(input.x.valInp);
|
||||
options.y = parseInt(input.y.valInp);
|
||||
});
|
||||
|
||||
// save the pixels of the base image
|
||||
var baseStepImage = this.getStep(options.offset).image;
|
||||
var baseStepOutput = this.getOutput(options.offset);
|
||||
@@ -30,19 +35,6 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
getPixels(input.src, function(err, pixels) {
|
||||
// parse the inputs
|
||||
parseCornerCoordinateInputs({
|
||||
iw: pixels.shape[0],
|
||||
ih: pixels.shape[1]
|
||||
},
|
||||
{
|
||||
x: { valInp: options.x, type: 'horizontal' },
|
||||
y: { valInp: options.y, type: 'vertical' },
|
||||
}, function(opt, input) {
|
||||
options.x = parseInt(input.x.valInp);
|
||||
options.y = parseInt(input.y.valInp);
|
||||
});
|
||||
|
||||
options.secondImagePixels = pixels;
|
||||
|
||||
function changePixel(r1, g1, b1, a1, x, y) {
|
||||
@@ -63,15 +55,11 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
return [r1, g1, b1, a1];
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
}
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// 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
|
||||
@@ -82,7 +70,7 @@ module.exports = function Dynamic(options, UI, util) {
|
||||
format: baseStepOutput.format,
|
||||
image: baseStepImage,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: modifiedCallback,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -15,10 +15,7 @@
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"desc": "offset to the output of the step on which the output of the last step is overlayed",
|
||||
"default": -2,
|
||||
"min": -2,
|
||||
"max": -1,
|
||||
"step": 1
|
||||
"default": -2
|
||||
}
|
||||
},
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#overlay-module"
|
||||
|
||||
@@ -16,8 +16,9 @@ module.exports = function PaintBucket(options, UI) {
|
||||
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
|
||||
@@ -2,7 +2,6 @@ module.exports = exports = function(pixels, options) {
|
||||
|
||||
|
||||
let defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
let fillColor = options.fillColor || defaults.fillColor,
|
||||
x = parseInt(options.startingX) || defaults.startingX,
|
||||
@@ -24,7 +23,7 @@ module.exports = exports = function(pixels, options) {
|
||||
minFactor = (1 - tolerance / 100);
|
||||
fillColor = fillColor.substring(fillColor.indexOf('(') + 1, fillColor.length - 1); // extract only the values from rgba(_,_,_,_)
|
||||
fillColor = fillColor.split(',');
|
||||
fillColor[3] = fillColor[3] * 255;
|
||||
|
||||
function isSimilar(currx, curry) {
|
||||
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 &&
|
||||
@@ -48,8 +47,10 @@ module.exports = exports = function(pixels, options) {
|
||||
} while (isSimilar(currx, south) && south < height);
|
||||
|
||||
for (n = north + 1; n < south; n += 1) {
|
||||
pixelSetter(currx, n, fillColor, pixels);
|
||||
|
||||
pixels.set(currx, n, 0, fillColor[0]);
|
||||
pixels.set(currx, n, 1, fillColor[1]);
|
||||
pixels.set(currx, n, 2, fillColor[2]);
|
||||
pixels.set(currx, n, 3, fillColor[3]);
|
||||
if (isSimilar(currx - 1, n)) {
|
||||
queuex.push(currx - 1);
|
||||
queuey.push(n);
|
||||
|
||||
@@ -18,8 +18,11 @@ module.exports = function ReplaceColor(options, UI) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype, wasmSuccess) {
|
||||
step.output = { src: datauri, format: mimetype, wasmSuccess, useWasm: options.useWasm };
|
||||
function output(image, datauri, mimetype) {
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
@@ -29,7 +32,6 @@ module.exports = function ReplaceColor(options, UI) {
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback,
|
||||
useWasm:options.useWasm
|
||||
});
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
module.exports = exports = function(pixels, options){
|
||||
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(_,_,_,_)
|
||||
|
||||
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(_,_,_,_)
|
||||
|
||||
var replaceMethod = options.replaceMethod || defaults.replaceMethod;
|
||||
var replaceMethod = options.replaceMethod || 'greyscale';
|
||||
color = color.split(',');
|
||||
replaceColor = replaceColor.split(',');
|
||||
|
||||
@@ -17,7 +14,7 @@ module.exports = exports = function(pixels, options){
|
||||
cg = color[1],
|
||||
cb = color[2];
|
||||
|
||||
var tolerance = options.tolerance || defaults.tolerance;
|
||||
var tolerance = options.tolerance || 50;
|
||||
var maxFactor = (1 + tolerance / 100);
|
||||
var minFactor = (1 - tolerance / 100);
|
||||
|
||||
@@ -26,6 +23,7 @@ module.exports = exports = function(pixels, options){
|
||||
g >= cg * minFactor && g <= cg * maxFactor &&
|
||||
b >= cb * minFactor && b <= cb * maxFactor);
|
||||
}
|
||||
|
||||
for(var i = 0; i < pixels.shape[0]; i++){
|
||||
for(var j = 0; j < pixels.shape[1]; j++){
|
||||
var r = pixels.get(i, j, 0),
|
||||
@@ -34,16 +32,16 @@ module.exports = exports = function(pixels, options){
|
||||
if(isSimilar(r, g, b)){
|
||||
if (replaceMethod == 'greyscale'){
|
||||
var avg = (r + g + b) / 3;
|
||||
pixelSetter(i, j, [avg, avg, avg], pixels);
|
||||
|
||||
pixels.set(i, j, 0, avg);
|
||||
pixels.set(i, j, 1, avg);
|
||||
pixels.set(i, j, 2, avg);
|
||||
}else {
|
||||
pixelSetter(i, j, replaceColor, pixels);
|
||||
|
||||
pixels.set(i, j, 0, replaceColor[0]);
|
||||
pixels.set(i, j, 1, replaceColor[1]);
|
||||
pixels.set(i, j, 2, replaceColor[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return pixels;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user