Added Section titles

master
Maksim 2019-01-28 19:59:20 -08:00
rodzic 35c849dcb9
commit ec8da1f3cc
5 zmienionych plików z 267 dodań i 222 usunięć

Wyświetl plik

@ -4,128 +4,156 @@
<div class="container">
<aside id="panel">
<div class="header">
<button class="btn btn-primary btn-circle"><i class="fa fa-angle-left"></i></button>
<!--<button class="btn btn-primary btn-circle"><i class="fa fa-angle-left"></i></button>-->
<h2>SquiggleCam</h2>
</div>
<image-chooser @selected="onInputSelected"></image-chooser>
<div class="accordion">
<div class="option">
<input type="checkbox" checked id="toggle1" class="toggle" />
<label class="title" for="toggle1">
Image Selection
</label>
<div class="content">
<div class="image-inputs">
<image-chooser @selected="onInputSelected"></image-chooser>
<div class="image-upload" v-if="this.inputType === 'upload'">
<croppa v-model="myCroppa" :width="settings.width/2" :height="settings.height/2" :preventWhiteSpace="true" :quality="2"></croppa>
<button class="btn btn-primary btn-block" style="display:block;" @click="uploadCroppedImage">Use Image</button>
</div>
<div class="image-upload" v-if="this.inputType === 'upload'">
<croppa v-model="cropper" :width="settings.width/2" :height="settings.height/2" :preventWhiteSpace="false" :quality="2"></croppa>
<button class="btn btn-primary btn-block" style="display:block;" @click="uploadCroppedImage">Use Image</button>
</div>
<div class="image-webcam" v-if="this.inputType === 'webcam'">
<div class="video-controls">
<select v-model="webcam.camera">
<option>- Select Device: -</option>
<option v-for="device in webcam.devices"
:key="device.deviceId"
:value="device.deviceId">{{ device.label }}</option>
</select>
<button v-if="webcam.streaming"
type="button"
class="btn btn-sm"
@click="onStop"><svg width="12" height="12"><rect width="12" height="12" style="fill:rgb(255,0,0)" />
</svg></button>
<button type="button"
class="btn btn-sm"
v-if="!webcam.streaming"
@click="onStart">Start</button>
<div class="image-webcam" v-if="this.inputType === 'webcam'">
<div class="video-controls">
<select v-model="webcam.camera">
<option>- Select Device: -</option>
<option v-for="device in webcam.devices"
:key="device.deviceId"
:value="device.deviceId">{{ device.label }}</option>
</select>
<button v-if="webcam.streaming"
type="button"
class="btn btn-sm"
@click="onStop"><svg width="12" height="12"><rect width="12" height="12" style="fill:rgb(255,0,0)" />
</svg></button>
<button type="button"
class="btn btn-sm"
v-if="!webcam.streaming"
@click="onStart">Start</button>
</div>
<webcam ref="webcam"
:device-id="webcam.deviceId"
:width="settings.width"
:height="settings.height"
@started="onStarted"
@stopped="onStopped"
@error="onError"
@cameras="onCameras"
@camera-change="onCameraChange" />
<button type="button"
class="btn btn-record"
v-if="webcam.streaming"
@click="onCapture">
<svg height="48" width="48" viewBox="0 0 48 48">
<circle cx="24" cy="24" r="23" stroke="white" stroke-width="1" fill="#D41616" />
</svg>
</button>
</div>
</div>
</div>
</div>
<webcam ref="webcam"
:device-id="webcam.deviceId"
:width="settings.width"
:height="settings.height"
@started="onStarted"
@stopped="onStopped"
@error="onError"
@cameras="onCameras"
@camera-change="onCameraChange" />
<div class="option">
<input type="checkbox" id="toggle2" class="toggle" />
<label class="title" for="toggle2">Paper Settings</label>
<div class="content">
<div class="slider">
<span class="label">
Width
</span>
<input type="range" min="200" max="500" v-model="settings.width">
<div class="output">{{ settings.width }}</div>
</div>
<button type="button"
class="btn btn-record"
v-if="webcam.streaming"
@click="onCapture">
<svg height="48" width="48" viewBox="0 0 48 48">
<circle cx="24" cy="24" r="23" stroke="white" stroke-width="1" fill="#D41616" />
</svg>
</button>
</div>
<div class="slider">
<span class="label">
Height
</span>
<input type="range" min="200" max="500" v-model="settings.height">
<div class="output">{{ settings.height }}</div>
</div>
</div>
</div>
<div class="section-title">
Squiggle Controls:
</div>
<div class="option">
<input type="checkbox" id="toggle3" class="toggle" />
<label class="title" for="toggle3">Image Adjustments</label>
<div class="content">
<div class="slider">
<span class="label">
Brightness
</span>
<input type="range" min="-100" max="100" v-model="settings.brightness">
<div class="output">{{ settings.brightness }}</div>
</div>
<div class="slider">
<span class="label">
Width
</span>
<input type="range" min="200" max="500" v-model="settings.width">
<div class="output">{{ settings.width }}</div>
</div>
<div class="slider">
<span class="label">
Contrast
</span>
<input type="range" min="-100" max="100" v-model="settings.contrast">
<div class="output">{{ settings.contrast }}</div>
</div>
<div class="slider">
<span class="label">
Height
</span>
<input type="range" min="200" max="500" v-model="settings.height">
<div class="output">{{ settings.height }}</div>
</div>
<div class="slider">
<span class="label">
Min brightness
</span>
<input type="range" min="0" max="255" v-model="settings.minBrightness">
<div class="output">{{ settings.minBrightness }}</div>
</div>
<div class="slider">
<span class="label">
Min brightness
</span>
<input type="range" min="0" max="255" v-model="settings.minBrightness">
<div class="output">{{ settings.minBrightness }}</div>
</div>
<div class="slider">
<span class="label">
Max brightness
</span>
<input type="range" min="0" max="255" v-model="settings.maxBrightness">
<div class="output">{{ settings.maxBrightness }}</div>
</div>
<div class="slider">
<span class="label">
Max brightness
</span>
<input type="range" min="0" max="255" v-model="settings.maxBrightness">
<div class="output">{{ settings.maxBrightness }}</div>
</div>
</div>
</div>
<div class="slider">
<span class="label">
Frequency
</span>
<input type="range" min="5" max="256" v-model="settings.frequency">
<div class="output">{{ settings.frequency }}</div>
</div>
<div class="slider">
<span class="label">
Line Count
</span>
<input type="range" min="10" max="200" v-model="settings.lineCount">
<div class="output">{{ settings.lineCount }}</div>
</div>
<div class="slider">
<span class="label">
Amplitude
</span>
<input type="range" min="0.1" max="5" step="0.1" v-model="settings.amplitude">
<div class="output">{{ settings.amplitude }}</div>
</div>
<div class="option">
<input type="checkbox" id="toggle4" class="toggle" />
<label class="title" for="toggle4">Squiggle Settings</label>
<div class="content">
<div class="slider">
<span class="label">
Frequency
</span>
<input type="range" min="5" max="256" v-model="settings.frequency">
<div class="output">{{ settings.frequency }}</div>
</div>
<div class="slider">
<span class="label">
Line Count
</span>
<input type="range" min="10" max="200" v-model="settings.lineCount">
<div class="output">{{ settings.lineCount }}</div>
</div>
<div class="slider">
<span class="label">
Amplitude
</span>
<input type="range" min="0.1" max="5" step="0.1" v-model="settings.amplitude">
<div class="output">{{ settings.amplitude }}</div>
</div>
</div>
</div>
<div class="slider">
<span class="label">
Smoothing
</span>
<input type="range" min="0" max="0.5" step="0.05" v-model="line.smoothing">
<div class="output">{{ line.smoothing}}</div>
</div>
<div class="slider">
<span class="label">
Flattening
</span>
<input type="range" min="0" max="1" step="0.01" v-model="line.flattening">
<div class="output">{{ line.flattening }}</div>
</div>
<div class="section-title">
@ -133,7 +161,7 @@
</div>
<div class="actions">
<button class="btn" @click="downloadSVG">
<button class="btn btn-block" @click="downloadSVG">
SVG
</button>
</div>
@ -163,18 +191,14 @@
},
data() {
return {
myCroppa: {},
cropper: {},
dataUrl: '',
line: {
smoothing: 0.25,
flattening: 0.5
},
lines: [],
inputType: "upload",
settings: {
frequency: 150,
amplitude: 1,
lineCount: 54,
lineCount: 50,
brightness: 0,
contrast: 0,
minBrightness: 0,
@ -216,6 +240,18 @@
'settings.amplitude': function(){
this.processImage();
},
'settings.minBrightess': function(){
this.processImage();
},
'settings.maxBrightness': function(){
this.processImage();
},
'settings.brightness': function(){
this.processImage();
},
'settings.contrast': function(){
this.processImage();
},
'canvasData': function(){
this.processImage();
}
@ -240,7 +276,7 @@
document.body.removeChild(downloadLink);
},
uploadCroppedImage() {
this.myCroppa.generateBlob((blob) => {
this.cropper.generateBlob((blob) => {
let canvas = document.createElement("canvas");
canvas.width = this.settings.width;
canvas.height = this.settings.height;
@ -263,9 +299,15 @@
// Gather all necessary data from the main thread
let config = data.config;
// context.getImageData(0, 0, config.WIDTH, config.HEIGHT);
let imagePixels = data.image;
let width = parseInt(config.width);
let height = parseInt(config.height);
const imagePixels = data.image;
const width = parseInt(config.width);
const height = parseInt(config.height);
const contrast = parseInt(config.contrast);
const brightness = parseInt(config.brightness);
const lineCount = parseInt(config.lineCount);
const minBrightness = parseInt(config.minBrightness);
const maxBrightness = parseInt(config.maxBrightness);
const spacing = parseFloat(config.spacing);
// Create some defaults for squiggle-point array
let squiggleData = [];
@ -276,8 +318,8 @@
let currentLine = []; // create empty array for storing x,y coordinate pairs
let currentVerticalPixelIndex = 0;
let currentHorizontalPixelIndex = 0;
let contrastFactor = (259 * (config.contrast + 255)) / (255 * (259 - config.contrast)); // This was established through experiments
let horizontalLineSpacing = Math.floor(height/parseInt(config.lineCount)); // Number of pixels to advance in vertical direction
let contrastFactor = (259 * (contrast + 255)) / (255 * (259 - contrast)); // This was established through experiments
let horizontalLineSpacing = Math.floor(height / lineCount); // Number of pixels to advance in vertical direction
//console.log(horizontalLineSpacing);
// Iterate line by line (top line to bottom line) in increments of horizontalLineSpacing
@ -292,32 +334,30 @@
// Loop through pixels from left to right within the current line, advancing by increments of config.SPACING
//console.log(config.spacing, width);
for (let x = config.spacing; x < width; x += config.spacing ) {
for (let x = spacing; x < width; x += 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 !== 0) {
if (contrast !== 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)) + (0.7154 * ((contrastFactor * (imagePixels.data[4 * (currentHorizontalPixelIndex + 1)] - 128) + 128)
+ config.brightness)) + (0.0721 * ((contrastFactor*(imagePixels.data[4*(currentHorizontalPixelIndex+2)]-128)+128) + config.brightness));
+ brightness)) + (0.7154 * ((contrastFactor * (imagePixels.data[4 * (currentHorizontalPixelIndex + 1)] - 128) + 128)
+ brightness)) + (0.0721 * ((contrastFactor*(imagePixels.data[4*(currentHorizontalPixelIndex+2)]-128)+128) + brightness));
} else {
b = (0.2125 * (imagePixels.data[4*currentHorizontalPixelIndex] + config.brightness)) + (0.7154 * (imagePixels.data[4*(currentHorizontalPixelIndex + 1)]+ config.brightness)) + (0.0721 * (imagePixels.data[4*(currentHorizontalPixelIndex + 2)] + config.brightness));
b = (0.2125 * (imagePixels.data[4*currentHorizontalPixelIndex] + brightness)) + (0.7154 * (imagePixels.data[4*(currentHorizontalPixelIndex + 1)]+ brightness)) + (0.0721 * (imagePixels.data[4*(currentHorizontalPixelIndex + 2)] + brightness));
}
b = Math.max(config.minBrightness,b); // Set minimum line curvature to value set by the user
z = Math.max(config.maxBrightness-b,0); // Set maximum line curvature to value set by the user
b = Math.max(minBrightness,b); // Set minimum line curvature to value set by the user
z = Math.max(maxBrightness-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.lineCount;
r = config.amplitude * z / lineCount;
a += z / config.frequency;
currentLine.push([x,y + Math.sin(a)*r]);
//currentLine.push([x,y + 10 * (tmpCounter%2 ? -1 : 1)]);
//tmpCounter++;
}
currentLine.push([config.width, y]);
//currentLine.push([config.width, y]);
squiggleData.push(currentLine);
}
@ -376,27 +416,4 @@
<style lang="scss">
@import './styles/index.scss';
/*.cropper-header {*/
/*background-color: #666;*/
/*height: 3rem;*/
/*overflow: hidden;*/
/*padding-left: 1rem;*/
/*padding-right: 1rem;*/
/*position: relative;*/
/*z-index: 1;*/
/*}*/
/*@media (min-width: 768px) {*/
/*.cropper-header {*/
/*padding-left: 1.5rem;*/
/*padding-right: 1.5rem;*/
/*}*/
/*}*/
.title {
color: #fff;
display: block;
float: left;
font-size: 1.125rem;
line-height: 3rem;
}
</style>

Wyświetl plik

@ -137,9 +137,6 @@
}
// Emit video start/live event
this.$refs.video.onloadedmetadata = () => {
// this.width = 500;
// this.height = 500;
this.$emit("video-live", stream);
};
@ -191,7 +188,6 @@
* load the Camera passed as index!
*/
loadCamera(device) {
console.log('loading camera, width', this.width);
navigator.mediaDevices
.getUserMedia({
video: {
@ -210,15 +206,12 @@
return this.getCanvas().toDataURL(this.screenshotFormat);
},
getCanvasRaw() {
//const { ctx, canvas } = this;
return this.ctx.getImageData(0,0,this.canvas.width, this.canvas.height);
},
getCanvas() {
let video = this.$refs.video;
if (!this.ctx) {
let canvas = document.createElement("canvas");
//console.log(video.width);
//console.log(video.height);
canvas.height = video.height;
canvas.width = video.width;
this.canvas = canvas;

Wyświetl plik

@ -1,7 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" :view-box.camel="viewbox" :width="width" :height="height">
<g>
<svg-chart-line :d="line" :o="options" v-for="(line, index) in lines" :key="index"></svg-chart-line>
<svg-chart-line :d="line" v-for="(line, index) in lines" :key="index"></svg-chart-line>
</g>
</svg>
</template>

Wyświetl plik

@ -3,48 +3,21 @@
</template>
<script>
import * as lib from "../lib";
export default {
name: "svgChartLine",
props: ["d", "o"],
props: ["d"],
computed: {
pathD() {
return this.pointsPositions.reduce((acc, e, i, a) => i === 0
? `M ${e[0]},${e[1]}`
: `${acc} ${this.bezierCommand(e, i, a)}`
, "");
},
pointsPositions() {
return this.d.values
}
},
methods: {
line(pointA, pointB) {
const lengthX = pointB[0] - pointA[0];
const lengthY = pointB[1] - pointA[1];
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
};
},
controlPoint(current, previous, next, reverse) {
const p = previous || current;
const n = next || current;
const o = this.line(p, n);
// work in progress
const flat = lib.map(Math.cos(o.angle) * this.o.flattening, 0, 1, 1, 0)
const angle = o.angle * flat + (reverse ? Math.PI : 0);
const length = o.length * this.o.smoothing;
const x = Math.round((current[0] + Math.cos(angle) * length) * 100) / 100 ;
const y = Math.round((current[1] + Math.sin(angle) * length) * 100) / 100 ;
return [x, y];
},
bezierCommand(point, i, a) {
const cps = this.controlPoint(a[i - 1], a[i - 2], point);
const cpe = this.controlPoint(point, a[i - 1], a[i + 1], true);
//const close = i === a.length - 1 ? " z" : "";
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${Math.round(point[0] * 100) / 100},${Math.round(point[1]*100)/100}`;
let path = "";
this.d.values.forEach((point, index) => {
if (index === 0) {
path += `M ${point[0]},${point[1]}`;
} else {
path += `L${Math.round(point[0] * 100) / 100},${Math.round(point[1]*100)/100}`
}
});
return path;
}
}
}

Wyświetl plik

@ -201,7 +201,8 @@ input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 5px;
cursor: pointer;
background-image: linear-gradient(-270deg, #fad961 0%, #f76b1c 100%);
background-color: #717171;
// background-image: linear-gradient(-270deg, #fad961 0%, #f76b1c 100%);
//box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
//background: #f76b1c;
//border-radius: 5px;
@ -218,18 +219,20 @@ input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -7.5px;
}
input[type="range"]:focus::-webkit-slider-runnable-track {
input[type="range"]:hover::-webkit-slider-runnable-track {
//background: #f8813e;
background-image: linear-gradient(-270deg, #fad961 0%, #f76b1c 100%);
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background-image: linear-gradient(-270deg, #fad961 0%, #f76b1c 100%);
}
input[type="range"]::-moz-range-track {
width: 100%;
height: 5px;
cursor: pointer;
//box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
background: #f76b1c;
background-image: linear-gradient(-270deg, #fad961 0%, #f76b1c 100%);
border-radius: 5px;
//border: 1px solid #18d501;
}
input[type="range"]::-moz-range-thumb {
box-shadow: 1.6px 1.6px 2.5px #cccccc, 0px 0px 1.6px #d9d9d9;
@ -250,15 +253,15 @@ input[type="range"]::-ms-track {
}
input[type="range"]::-ms-fill-lower {
background: #e75908;
border: 0px solid #18d501;
border: none;
border-radius: 2px;
box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
//box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
}
input[type="range"]::-ms-fill-upper {
background: #f76b1c;
border: 0px solid #18d501;
border: none;
border-radius: 2px;
box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
//box-shadow: 0px 0px 0px #6f2200, 0px 0px 0px #892a00;
}
input[type="range"]::-ms-thumb {
box-shadow: 1.6px 1.6px 2.5px #cccccc, 0px 0px 1.6px #d9d9d9;
@ -268,7 +271,6 @@ input[type="range"]::-ms-thumb {
border-radius: 10px;
background: #298bff;
cursor: pointer;
height: 5px;
}
input[type="range"]:focus::-ms-fill-lower {
background: #f76b1c;
@ -277,14 +279,14 @@ input[type="range"]:focus::-ms-fill-upper {
background: #f8813e;
}
.image-inputs {
margin-bottom: 15px;
}
.actions {
display: flex;
justify-content: space-between;
// & > * {
// //flex: 1;
//}
padding-bottom: 20px;
.btn {
background-color: $btn-bg;
color: $btn-text-color;
@ -292,34 +294,40 @@ input[type="range"]:focus::-ms-fill-upper {
&:hover {
background-color: #6b6b6b;
}
}
.btn-block{
width: 100%;
display: block;
}
}
}
.section-title {
margin-top: 25px;
margin-top: 15px;
margin-bottom: 15px;
text-transform: uppercase;
}
.slider {
position: relative;
height: 65px;
display: flex;
margin-bottom: 7px;
.label {
position: absolute;
top: 5px;
top: 8px;
line-height: 18px;
right: 0px;
font-family: Monaco;
right: 0;
font-family: Monaco, monospace;
font-size: 12px;
color: #d2d2d2;
text-align: right;
}
input[type="range"] {
margin-top: 18px;
margin-top: 35px;
}
.output {
@ -333,7 +341,7 @@ input[type="range"]:focus::-ms-fill-upper {
font-family: monaco, Consolas, Lucida Console, monospace;
font-size: 14px;
line-height: 20px;
}
}
}
.header {
@ -351,6 +359,7 @@ input[type="range"]:focus::-ms-fill-upper {
.image-webcam {
position: relative;
margin-bottom: 30px;
.btn-record {
position: absolute;
@ -362,3 +371,56 @@ input[type="range"]:focus::-ms-fill-upper {
}
}
.toggle {
display: none;
}
.option {
position: relative;
margin-bottom: 2px;
}
.title {
text-transform: uppercase;
text-align: left;
background-color: $btn-bg;
color: $btn-text-color;
padding: 10px;
}
.title,
.content {
backface-visibility: hidden;
transform: translateZ(0);
transition: all 0.3s;
display: block;
}
.title:before {
content: '';
border-style: solid;
border-width: 2px 2px 0 0;
position: absolute;
right: 15px;
top: 15px;
width: 12px;
height: 12px;
transform: rotate(45deg);
transform-origin: 75% 20%;
transition: transform 0.3s;
}
.content {
max-height: 0;
overflow: hidden;
}
.toggle:checked {
& + .title + .content {
max-height: 500px;
}
& + .title:before {
transform: rotate(135deg) !important;
}
}