Added download button!

gh-pages
Maks Surguy 2017-12-18 01:37:45 -08:00
rodzic c7f91c88ed
commit af0b22f646
8 zmienionych plików z 23620 dodań i 28 usunięć

467
dist/app.css vendored 100644
Wyświetl plik

@ -0,0 +1,467 @@
body {
width: 100%;
margin: 0;
padding: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
svg {
z-index: 1;
}
video {
width: 100px;
height: 100px;
display: block;
}
.settings-container {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.status-container {
-webkit-box-flex: 0;
-ms-flex: 0 0 100px;
flex: 0 0 100px;
}
#status {
display: inline-block;
width: 100px;
text-align: center;
padding-bottom: 10px;
}
p {
margin: 0;
padding: 0;
font-size: 12px;
}
.actions {
background-color: black;
color: white;
z-index: 100;
padding: 0;
margin: 0;
width: 100%;
height: 210px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.sliders {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.col-50 {
position: relative;
margin: 0;
padding: 10px;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
#drawButton {
margin-top: 10px;
}
.rangeslider {
position: relative;
display: block;
cursor: pointer;
height: 25px;
width: 100%;
}
.rangeslider__fill,
.rangeslider__fill__bg {
display: block;
position: absolute;
top: 50%;
height: 2px;
z-index: 2;
background: #29e;
border-radius: 10px;
will-change: width;
}
.rangeslider__handle {
will-change: width, height, top;
width: 18px;
height: 18px;
position: absolute;
top: 50%;
background: #29e;
display: inline-block;
z-index: 3;
cursor: pointer;
border: solid 2px #ffffff;
border-radius: 50%;
-webkit-transition: width 0.1s ease-in-out, height 0.1s ease-in-out, top 0.1s ease-in-out;
transition: width 0.1s ease-in-out, height 0.1s ease-in-out, top 0.1s ease-in-out;
}
.rangeslider__handle:active {
background: #107ecd;
}
.rangeslider__fill__bg {
background: #ccc;
width: 100%;
}
.rangeslider--disabled {
opacity: 0.4;
}
.btn {
display: inline-block;
padding: 8px 8px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
}
.btn:focus,
.btn:active:focus,
.btn.active:focus,
.btn.focus,
.btn:active.focus,
.btn.active.focus {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.btn:hover,
.btn:focus,
.btn.focus {
color: #333;
text-decoration: none;
}
.btn:active,
.btn.active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn.disabled,
.btn[disabled],
fieldset[disabled] .btn {
cursor: not-allowed;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
opacity: .65;
}
a.btn.disabled,
fieldset[disabled] a.btn {
pointer-events: none;
}
.btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
}
.btn-primary:focus,
.btn-primary.focus {
color: #fff;
background-color: #286090;
border-color: #122b40;
}
.btn-primary:hover {
color: #fff;
background-color: #286090;
border-color: #204d74;
}
.btn-primary:active,
.btn-primary.active,
.open > .dropdown-toggle.btn-primary {
color: #fff;
background-color: #286090;
border-color: #204d74;
}
.btn-primary:active:hover,
.btn-primary.active:hover,
.open > .dropdown-toggle.btn-primary:hover,
.btn-primary:active:focus,
.btn-primary.active:focus,
.open > .dropdown-toggle.btn-primary:focus,
.btn-primary:active.focus,
.btn-primary.active.focus,
.open > .dropdown-toggle.btn-primary.focus {
color: #fff;
background-color: #204d74;
border-color: #122b40;
}
.btn-primary:active,
.btn-primary.active,
.open > .dropdown-toggle.btn-primary {
background-image: none;
}
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus {
background-color: #337ab7;
border-color: #2e6da4;
}
.btn-success {
color: #fff;
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-success:focus,
.btn-success.focus {
color: #fff;
background-color: #449d44;
border-color: #255625;
}
.btn-success:hover {
color: #fff;
background-color: #449d44;
border-color: #398439;
}
.btn-success:active,
.btn-success.active,
.open > .dropdown-toggle.btn-success {
color: #fff;
background-color: #449d44;
border-color: #398439;
}
.btn-success:active:hover,
.btn-success.active:hover,
.open > .dropdown-toggle.btn-success:hover,
.btn-success:active:focus,
.btn-success.active:focus,
.open > .dropdown-toggle.btn-success:focus,
.btn-success:active.focus,
.btn-success.active.focus,
.open > .dropdown-toggle.btn-success.focus {
color: #fff;
background-color: #398439;
border-color: #255625;
}
.btn-success:active,
.btn-success.active,
.open > .dropdown-toggle.btn-success {
background-image: none;
}
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus {
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-info {
color: #fff;
background-color: #5bc0de;
border-color: #46b8da;
}
.btn-info:focus,
.btn-info.focus {
color: #fff;
background-color: #31b0d5;
border-color: #1b6d85;
}
.btn-info:hover {
color: #fff;
background-color: #31b0d5;
border-color: #269abc;
}
.btn-info:active,
.btn-info.active,
.open > .dropdown-toggle.btn-info {
color: #fff;
background-color: #31b0d5;
border-color: #269abc;
}
.btn-info:active:hover,
.btn-info.active:hover,
.open > .dropdown-toggle.btn-info:hover,
.btn-info:active:focus,
.btn-info.active:focus,
.open > .dropdown-toggle.btn-info:focus,
.btn-info:active.focus,
.btn-info.active.focus,
.open > .dropdown-toggle.btn-info.focus {
color: #fff;
background-color: #269abc;
border-color: #1b6d85;
}
.btn-info:active,
.btn-info.active,
.open > .dropdown-toggle.btn-info {
background-image: none;
}
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus {
background-color: #5bc0de;
border-color: #46b8da;
}
.btn-danger {
color: #fff;
background-color: #d9534f;
border-color: #d43f3a;
}
.btn-danger:focus,
.btn-danger.focus {
color: #fff;
background-color: #c9302c;
border-color: #761c19;
}
.btn-danger:hover {
color: #fff;
background-color: #c9302c;
border-color: #ac2925;
}
.btn-danger:active,
.btn-danger.active,
.open > .dropdown-toggle.btn-danger {
color: #fff;
background-color: #c9302c;
border-color: #ac2925;
}
.btn-danger:active:hover,
.btn-danger.active:hover,
.open > .dropdown-toggle.btn-danger:hover,
.btn-danger:active:focus,
.btn-danger.active:focus,
.open > .dropdown-toggle.btn-danger:focus,
.btn-danger:active.focus,
.btn-danger.active.focus,
.open > .dropdown-toggle.btn-danger.focus {
color: #fff;
background-color: #ac2925;
border-color: #761c19;
}
.btn-danger:active,
.btn-danger.active,
.open > .dropdown-toggle.btn-danger {
background-image: none;
}
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus {
background-color: #d9534f;
border-color: #d43f3a;
}
a {
background-color: transparent;
}
a:active,
a:hover {
outline: 0;
}
a {
color: #337ab7;
text-decoration: none;
}
a:hover,
a:focus {
color: #23527c;
text-decoration: underline;
}
a:focus {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.btn-block {
display: block;
width: 100%;
}

22911
dist/app.js vendored 100644

File diff suppressed because one or more lines are too long

151
dist/imageAnalyzer.js vendored 100644
Wyświetl plik

@ -0,0 +1,151 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 467);
/******/ })
/************************************************************************/
/******/ ({
/***/ 467:
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(468);
/***/ }),
/***/ 468:
/***/ (function(module, exports) {
/**
* imageAnalyzer Web Worker accepts image data and configuration parameters
* and returns array of x,y coordinates to draw squiggles
*
* It analyzes pixels for their average brightness and if brightness is closer to 0 -> makes lines less
* squiggly, if closer to 255 -> more squiggly
*
* This script runs in a webWorker thread in order to speed up processing of the image and
* to detach the processing from the UI updates
*
* Majority of the logic in this script is taken from https://github.com/gwygonik/SquiggleDraw for the following exceptions:
* - I implemented contrast adjustment that can be helpful in certain light conditions
* - I increased the processing speed by removing unnecessary assignments and moving variable
* declarations out of the processing loop
*/
self.addEventListener('message', function (e) {
// Gather all necessary data from the main thread
var config = e.data.config;
var imagePixels = e.data.data;
var width = e.data.config.WIDTH;
var height = e.data.config.HEIGHT;
// Create some defaults for squiggle-point array
var squiggleData = [];
var r = 5;
var a = 0;
var b = void 0;
var z = void 0;
var currentCoordinates = []; // create empty array for storing x,y coordinate pairs
var currentVerticalPixelIndex = 0;
var currentHorizontalPixelIndex = 0;
var contrastFactor = 259 * (config.CONTRAST_ADJUSTMENT + 255) / (255 * (259 - config.CONTRAST_ADJUSTMENT)); // This was established through experiments
var horizontalLineSpacing = Math.floor(height / config.LINE_COUNT); // Number of pixels to advance in vertical direction
// Iterate line by line (top line to bottom line) in increments of horizontalLineSpacing
for (var y = 0; y < height; y += horizontalLineSpacing) {
a = 0;
currentCoordinates = [];
currentCoordinates.push([0, y]); // Start the line
currentVerticalPixelIndex = y * width; // Because Image Pixel array is of length width * height,
// starting pixel for each line will be this
// Loop through pixels from left to right within the current line, advancing by increments of config.SPACING
for (var x = 1; x < width; x += config.SPACING) {
currentHorizontalPixelIndex = x + currentVerticalPixelIndex; // Get array position of current pixel
// When there is contrast adjustment, the calculations of brightness values are a bit different
if (config.CONTRAST_ADJUSTMENT !== 0) {
// Determine how bright a pixel is, from 0 to 255 by summing three channels (R,G,B) multiplied by some coefficients
b = 0.2125 * (contrastFactor * (imagePixels.data[4 * currentHorizontalPixelIndex] - 128) + 128 + config.BRIGHTNESS_ADJUSTMENT) + 0.7154 * (contrastFactor * (imagePixels.data[4 * (currentHorizontalPixelIndex + 1)] - 128) + 128 + config.BRIGHTNESS_ADJUSTMENT) + 0.0721 * (contrastFactor * (imagePixels.data[4 * (currentHorizontalPixelIndex + 2)] - 128) + 128 + config.BRIGHTNESS_ADJUSTMENT);
} else {
b = 0.2125 * (imagePixels.data[4 * currentHorizontalPixelIndex] + config.BRIGHTNESS_ADJUSTMENT) + 0.7154 * (imagePixels.data[4 * (currentHorizontalPixelIndex + 1)] + config.BRIGHTNESS_ADJUSTMENT) + 0.0721 * (imagePixels.data[4 * (currentHorizontalPixelIndex + 2)] + config.BRIGHTNESS_ADJUSTMENT);
}
b = Math.max(config.MIN_BRIGHTNESS, b); // Set minimum line curvature to value set by the user
z = Math.max(config.MAX_BRIGHTNESS - b, 0); // Set maximum line curvature to value set by the user
// The magic of the script, determines how high / low the squiggle goes
r = config.AMPLITUDE * z / config.LINE_COUNT;
a += z / config.FREQUENCY;
currentCoordinates.push([x, y + Math.sin(a) * r]);
}
squiggleData.push(currentCoordinates);
}
self.postMessage(squiggleData);
}, false);
/***/ })
/******/ });

65
dist/index.html vendored 100644
Wyświetl plik

@ -0,0 +1,65 @@
<html>
<head>
<meta charset="UTF-8">
<title>SquiggleCam - capture a selfie, turn it into art</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="app.css">
</head>
<body>
<canvas id="canvas" width="800" height="600" style="display: none;"></canvas>
<div class="actions">
<div class="settings-container">
<div id="settings">
<div class="sliders">
<div class="col-50">
<label for="lineCount">Line Count (1...200):</label> <span id="lineCountValue">90</span>
<input id="lineCount" name="lineCount" type="range" min="1" max="200" value="90" step="1">
</div>
<div class="col-50">
<label for="amplitude">Amplitude (0.1...5): </label> <span id="amplitudeValue">1.2</span>
<input name="amplitude" id="amplitude" type="range" min="0.1" max="5" value="1.2" step="0.05">
</div>
</div>
<div class="sliders">
<div class="col-50">
<label for="lineSpacing">Pixel Spacing (0.5...5):</label> <span id="lineSpacingValue">1</span>
<input id="lineSpacing" name="lineSpacing" type="range" min="0.5" max="5" value="1" step="0.5">
</div>
<div class="col-50">
<label for="frequency">Frequency (5...256): </label> <span id="frequencyValue">135</span>
<input name="frequency" id="frequency" type="range" min="5" max="256" value="135" step="1">
</div>
</div>
<div class="sliders">
<div class="col-50">
<label for="brightness">Brightness (-100...100):</label> <span id="brightnessValue">0</span>
<input id="brightness" name="brightness" type="range" min="-100" max="100" value="0" step="1">
</div>
<div class="col-50">
<label for="contrast">Contrast (-100...100): </label> <span id="contrastValue">0</span>
<input id="contrast" name="contrast" type="range" min="-100" max="100" value="0" step="1">
</div>
</div>
</div>
</div>
<div class="status-container">
<div id="status">
<video autoplay width="800" height="600"></video>
<a href="#" id="captureButton" class="btn btn-lg btn-success">Capture</a>
<a href="#" id="drawButton" class="btn btn-lg btn-primary" style="display: none;">Download</a>
</div>
</div>
</div>
<div id="svg-placeholder"></div>
<script src="app.js"></script>
<script src="//localhost:35729/livereload.js"></script>
</body>
</html>

Wyświetl plik

@ -0,0 +1,6 @@
{
"/dist/app.js": "/dist/app.js",
"/dist/app.css": "/dist/app.css",
"/dist/imageAnalyzer.js": "/dist/imageAnalyzer.js",
"/dist/index.html": "/dist/index.html"
}

Wyświetl plik

@ -131,9 +131,19 @@ drawButton.addEventListener('click', () => {
const blob = new Blob([svgDoctype + svgString], {type: 'image/svg+xml;charset=utf-8'});
console.log(blob);
/* This portion of script saves the file to local filesystem as a download */
const svgUrl = URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = "squiggleCam_" + Date.now() + ".svg";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
/*
// If you wanted to upload the image instead, use something like this:
// If you wanted to upload the image to a server instead, use something like this:
const oReq = new XMLHttpRequest();
oReq.open("POST", serverIPAddress + ':3000/upload', true);
@ -153,68 +163,50 @@ drawButton.addEventListener('click', () => {
// Attach listener to each of the range sliders
rangesliderJs.create(lineCountValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
lineCountValueEl.innerHTML = val;
config.LINE_COUNT = val;
processImage();
},
onSlide: (val) => {
lineCountValueEl.innerHTML = val;
}
});
rangesliderJs.create(amplitudeValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
amplitudeValueEl.innerHTML = val;
config.AMPLITUDE = val;
processImage();
},
onSlide: (val) => {
amplitudeValueEl.innerHTML = val;
}
});
rangesliderJs.create(lineSpacingValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
lineSpacingValueEl.innerHTML = val;
config.SPACING = val;
processImage();
},
onSlide: (val) => {
lineSpacingValueEl.innerHTML = val;
}
});
rangesliderJs.create(frequencyValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
frequencyValueEl.innerHTML = val;
config.FREQUENCY = val;
processImage();
},
onSlide: (val) => {
frequencyValueEl.innerHTML = val;
}
});
rangesliderJs.create(brightnessValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
brightnessValueEl.innerHTML = val;
config.BRIGHTNESS_ADJUSTMENT = val;
processImage();
},
onSlide: (val) => {
brightnessValueEl.innerHTML = val;
}
});
rangesliderJs.create(contrastValue, {
onSlideEnd: (val) => {
onSlide: (val) => {
contrastValueEl.innerHTML = val;
config.CONTRAST_ADJUSTMENT = val;
processImage();
},
onSlide: (val) => {
contrastValueEl.innerHTML = val;
}
});

Wyświetl plik

@ -60,7 +60,7 @@ self.addEventListener('message', function(e) {
z = Math.max(config.MAX_BRIGHTNESS-b,0); // Set maximum line curvature to value set by the user
// The magic of the script, determines how high / low the squiggle goes
r = z / (config.LINE_COUNT * config.AMPLITUDE);
r = config.AMPLITUDE * z / config.LINE_COUNT;
a += z / config.FREQUENCY;
currentCoordinates.push([x,y + Math.sin(a)*r]);

Wyświetl plik

@ -10,7 +10,7 @@
<link rel="stylesheet" href="app.css">
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<canvas id="canvas" width="800" height="600" style="display: none;"></canvas>
<div class="actions">
<div class="settings-container">
<div id="settings">