mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-27 22:41:20 -07:00
Touch
This commit is contained in:
+179
-179
@@ -1,185 +1,185 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>VTracer</title>
|
<title>VTracer</title>
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="./assets/apple-icon.png">
|
<link rel="apple-touch-icon" sizes="192x192" href="./assets/apple-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="./assets/apple-icon.png">
|
<link rel="icon" type="image/png" sizes="192x192" href="./assets/apple-icon.png">
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.button {
|
.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
#droptext {
|
#droptext {
|
||||||
border: 2px dashed #000;
|
border: 2px dashed #000;
|
||||||
}
|
}
|
||||||
#droptext.hovering {
|
#droptext.hovering {
|
||||||
border: 2px dashed #fff;
|
border: 2px dashed #fff;
|
||||||
}
|
}
|
||||||
#canvas-container {
|
#canvas-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 2.5%;
|
left: 2.5%;
|
||||||
top: 5%;
|
top: 5%;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
height: 90%;
|
height: 90%;
|
||||||
}
|
}
|
||||||
#svg, #frame {
|
#svg, #frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
#svg > path:hover {
|
#svg > path:hover {
|
||||||
stroke: #ff0;
|
stroke: #ff0;
|
||||||
}
|
}
|
||||||
#droptext {
|
#droptext {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#options {
|
#options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 2.5%;
|
left: 2.5%;
|
||||||
top: 5%;
|
top: 5%;
|
||||||
padding: 50px;
|
padding: 50px;
|
||||||
background: rgba(255,255,255,0.5);
|
background: rgba(255,255,255,0.5);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<input type="file" id="imageInput" accept="image/*" style="display: none;">
|
<input type="file" id="imageInput" accept="image/*" style="display: none;">
|
||||||
<div>
|
|
||||||
<div id="progressregion" style="display: none;">
|
|
||||||
<progress id="progressbar" value="0" max="100" style="width: 98%;"></progress>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<div id="drop">
|
<div id="progressregion" style="display: none;">
|
||||||
<div id="canvas-container">
|
<progress id="progressbar" value="0" max="100" style="width: 98%;"></progress>
|
||||||
<div id="droptext">
|
</div>
|
||||||
<p>Drag an image here or <a href="#" id="imageSelect">Select file</a></p>
|
<div>
|
||||||
|
<div id="drop">
|
||||||
|
<div id="canvas-container">
|
||||||
|
<div id="droptext">
|
||||||
|
<p>Drag an image here or <a href="#" id="imageSelect">Select file</a></p>
|
||||||
|
</div>
|
||||||
|
<canvas id="frame"></canvas>
|
||||||
|
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||||
</div>
|
</div>
|
||||||
<canvas id="frame"></canvas>
|
|
||||||
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="options">
|
||||||
|
<div>
|
||||||
|
<a class="button" id="export">Download as SVG</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div title="Algorithm for segmentation and grouping of pixel clusters">
|
||||||
|
Clustering
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<button id="clustering-binary" title="Black & White (Binary Image)">B/W</button>
|
||||||
|
<button id="clustering-color" title="True Color Image">Color</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div title="Discard patches small than X px in size">
|
||||||
|
Filter Speckle <span>(Cleaner)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="filterspecklevalue">
|
||||||
|
4
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="filterspeckle" type="range" min="1" max="16" step="1" value="4">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clustering-color-options">
|
||||||
|
<div title="Number of significant bits to use in a RGB channel">
|
||||||
|
Color Precision <span>(More accurate)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="colorprecisionvalue" class="clustering-color-options">
|
||||||
|
6
|
||||||
|
</div>
|
||||||
|
<div class="clustering-color-options">
|
||||||
|
<input id="colorprecision" type="range" min="1" max="8" step="1" value="6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clustering-color-options">
|
||||||
|
<div title="Color difference between gradient layers">
|
||||||
|
Gradient Step <span>(Less layers)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="layerdifferencevalue" class="clustering-color-options">
|
||||||
|
16
|
||||||
|
</div>
|
||||||
|
<div class="clustering-color-options">
|
||||||
|
<input id="layerdifference" type="range" min="0" max="255" step="1" value="16">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div title="Algorithm for converting clusters to shapes">
|
||||||
|
Curve Fitting
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<button id="none" title="Exact cluster boundary">Pixel</button>
|
||||||
|
<button id="polygon" title="Simplify to Polygon">Polygon</button>
|
||||||
|
<button id="spline" class="selected" title="Smooth and Curve-fit">Spline</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spline-options">
|
||||||
|
<div title="Minimum Momentary Angle (in degrees) to be considered a corner (to be kept after smoothing)">
|
||||||
|
Corner Threshold <span>(Smoother)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="cornervalue" class="spline-options">
|
||||||
|
60
|
||||||
|
</div>
|
||||||
|
<div class="spline-options">
|
||||||
|
<input id="corner" type="range" min="0" max="180" step="1" value="60">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spline-options">
|
||||||
|
<div title="Perform Iterative Subdivide Smooth until all segments are shorter than this length">
|
||||||
|
Segment Length <span>(More coarse)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lengthvalue" class="spline-options">
|
||||||
|
4
|
||||||
|
</div>
|
||||||
|
<div class="spline-options">
|
||||||
|
<input id="length" type="range" min="3.5" max="10" step="0.5" value="4">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spline-options">
|
||||||
|
<div title="Minimum Angle Displacement (in degrees) to be considered a cutting point between curves">
|
||||||
|
Splice Threshold <span>(More accurate)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="splicevalue" class="spline-options">
|
||||||
|
45
|
||||||
|
</div>
|
||||||
|
<div class="spline-options">
|
||||||
|
<input id="splice" type="range" min="0" max="180" step="1" value="45">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="options">
|
<script src="./bootstrap.js"></script>
|
||||||
<div>
|
</body>
|
||||||
<a class="button" id="export">Download as SVG</a>
|
</html>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p></p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div title="Algorithm for segmentation and grouping of pixel clusters">
|
|
||||||
Clustering
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<button id="clustering-binary" title="Black & White (Binary Image)">B/W</button>
|
|
||||||
<button id="clustering-color" title="True Color Image">Color</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div title="Discard patches small than X px in size">
|
|
||||||
Filter Speckle <span>(Cleaner)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="filterspecklevalue">
|
|
||||||
4
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="filterspeckle" type="range" min="1" max="16" step="1" value="4">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="clustering-color-options">
|
|
||||||
<div title="Number of significant bits to use in a RGB channel">
|
|
||||||
Color Precision <span>(More accurate)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="colorprecisionvalue" class="clustering-color-options">
|
|
||||||
6
|
|
||||||
</div>
|
|
||||||
<div class="clustering-color-options">
|
|
||||||
<input id="colorprecision" type="range" min="1" max="8" step="1" value="6">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="clustering-color-options">
|
|
||||||
<div title="Color difference between gradient layers">
|
|
||||||
Gradient Step <span>(Less layers)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="layerdifferencevalue" class="clustering-color-options">
|
|
||||||
16
|
|
||||||
</div>
|
|
||||||
<div class="clustering-color-options">
|
|
||||||
<input id="layerdifference" type="range" min="0" max="255" step="1" value="16">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p></p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div title="Algorithm for converting clusters to shapes">
|
|
||||||
Curve Fitting
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<button id="none" title="Exact cluster boundary">Pixel</button>
|
|
||||||
<button id="polygon" title="Simplify to Polygon">Polygon</button>
|
|
||||||
<button id="spline" class="selected" title="Smooth and Curve-fit">Spline</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spline-options">
|
|
||||||
<div title="Minimum Momentary Angle (in degrees) to be considered a corner (to be kept after smoothing)">
|
|
||||||
Corner Threshold <span>(Smoother)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="cornervalue" class="spline-options">
|
|
||||||
60
|
|
||||||
</div>
|
|
||||||
<div class="spline-options">
|
|
||||||
<input id="corner" type="range" min="0" max="180" step="1" value="60">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spline-options">
|
|
||||||
<div title="Perform Iterative Subdivide Smooth until all segments are shorter than this length">
|
|
||||||
Segment Length <span>(More coarse)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="lengthvalue" class="spline-options">
|
|
||||||
4
|
|
||||||
</div>
|
|
||||||
<div class="spline-options">
|
|
||||||
<input id="length" type="range" min="3.5" max="10" step="0.5" value="4">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spline-options">
|
|
||||||
<div title="Minimum Angle Displacement (in degrees) to be considered a cutting point between curves">
|
|
||||||
Splice Threshold <span>(More accurate)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="splicevalue" class="spline-options">
|
|
||||||
45
|
|
||||||
</div>
|
|
||||||
<div class="spline-options">
|
|
||||||
<input id="splice" type="range" min="0" max="180" step="1" value="45">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="./bootstrap.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
+285
-285
@@ -1,294 +1,294 @@
|
|||||||
import { BinaryImageConverter, ColorImageConverter } from 'vtracer';
|
import { BinaryImageConverter, ColorImageConverter } from 'vtracer';
|
||||||
|
|
||||||
let runner;
|
let runner;
|
||||||
const canvas = document.getElementById('frame');
|
const canvas = document.getElementById('frame');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const svg = document.getElementById('svg');
|
const svg = document.getElementById('svg');
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const progress = document.getElementById('progressbar');
|
const progress = document.getElementById('progressbar');
|
||||||
const progressregion = document.getElementById('progressregion');
|
const progressregion = document.getElementById('progressregion');
|
||||||
let mode = 'spline', clustering_mode = 'color';
|
let mode = 'spline', clustering_mode = 'color';
|
||||||
|
|
||||||
// Hide canas and svg on load
|
// Hide canas and svg on load
|
||||||
canvas.style.display = 'none';
|
canvas.style.display = 'none';
|
||||||
svg.style.display = 'none';
|
svg.style.display = 'none';
|
||||||
|
|
||||||
// Paste from clipboard
|
// Paste from clipboard
|
||||||
document.addEventListener('paste', function (e) {
|
document.addEventListener('paste', function (e) {
|
||||||
if (e.clipboardData) {
|
if (e.clipboardData) {
|
||||||
var items = e.clipboardData.items;
|
var items = e.clipboardData.items;
|
||||||
if (!items) return;
|
if (!items) return;
|
||||||
|
|
||||||
//access data directly
|
//access data directly
|
||||||
for (var i = 0; i < items.length; i++) {
|
for (var i = 0; i < items.length; i++) {
|
||||||
if (items[i].type.indexOf("image") !== -1) {
|
if (items[i].type.indexOf("image") !== -1) {
|
||||||
//image
|
//image
|
||||||
var blob = items[i].getAsFile();
|
var blob = items[i].getAsFile();
|
||||||
var URLObj = window.URL || window.webkitURL;
|
var URLObj = window.URL || window.webkitURL;
|
||||||
var source = URLObj.createObjectURL(blob);
|
var source = URLObj.createObjectURL(blob);
|
||||||
setSourceAndRestart(source);
|
setSourceAndRestart(source);
|
||||||
}
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Download as SVG
|
|
||||||
document.getElementById('export').addEventListener('click', function (e) {
|
|
||||||
const blob = new Blob([new XMLSerializer().serializeToString(svg)], {type: 'octet/stream'}),
|
|
||||||
url = window.URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
this.href = url;
|
|
||||||
this.target = '_blank';
|
|
||||||
|
|
||||||
this.download = 'export-' + new Date().toISOString().slice(0, 19).replace(/:/g, '').replace('T', ' ') + '.svg';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to load a given config WITHOUT restarting
|
|
||||||
function loadConfig(config) {
|
|
||||||
mode = config.mode;
|
|
||||||
clustering_mode = config.clustering_mode;
|
|
||||||
|
|
||||||
globalcorner = config.corner_threshold;
|
|
||||||
document.getElementById('cornervalue').innerHTML = globalcorner;
|
|
||||||
document.getElementById('corner').value = globalcorner;
|
|
||||||
|
|
||||||
globallength = config.length_threshold;
|
|
||||||
document.getElementById('lengthvalue').innerHTML = globallength;
|
|
||||||
document.getElementById('length').value = globallength;
|
|
||||||
|
|
||||||
globalsplice = config.splice_threshold;
|
|
||||||
document.getElementById('splicevalue').innerHTML = globalsplice;
|
|
||||||
document.getElementById('splice').value = globalsplice;
|
|
||||||
|
|
||||||
globalfilterspeckle = config.filter_speckle;
|
|
||||||
document.getElementById('filterspecklevalue').innerHTML = globalfilterspeckle;
|
|
||||||
document.getElementById('filterspeckle').value = globalfilterspeckle;
|
|
||||||
|
|
||||||
globalcolorprecision = config.color_precision;
|
|
||||||
document.getElementById('colorprecisionvalue').innerHTML = globalcolorprecision;
|
|
||||||
document.getElementById('colorprecision').value = globalcolorprecision;
|
|
||||||
|
|
||||||
globallayerdifference = config.layer_difference;
|
|
||||||
document.getElementById('layerdifferencevalue').innerHTML = globallayerdifference;
|
|
||||||
document.getElementById('layerdifference').value = globallayerdifference;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload button
|
|
||||||
var imageSelect = document.getElementById('imageSelect'),
|
|
||||||
imageInput = document.getElementById('imageInput');
|
|
||||||
imageSelect.addEventListener('click', function (e) {
|
|
||||||
imageInput.click();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
imageInput.addEventListener('change', function (e) {
|
|
||||||
setSourceAndRestart(this.files[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drag-n-Drop
|
|
||||||
var drop = document.getElementById('drop');
|
|
||||||
var droptext = document.getElementById('droptext');
|
|
||||||
drop.addEventListener('dragenter', function (e) {
|
|
||||||
if (e.preventDefault) e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
|
||||||
droptext.classList.add('hovering');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
drop.addEventListener('dragleave', function (e) {
|
|
||||||
if (e.preventDefault) e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
|
||||||
droptext.classList.remove('hovering');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
drop.addEventListener('dragover', function (e) {
|
|
||||||
if (e.preventDefault) e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
|
||||||
droptext.classList.add('hovering');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
drop.addEventListener('drop', function (e) {
|
|
||||||
if (e.preventDefault) e.preventDefault();
|
|
||||||
droptext.classList.remove('hovering');
|
|
||||||
setSourceAndRestart(e.dataTransfer.files[0]);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get Input from UI controls
|
|
||||||
var globalcorner = parseInt(document.getElementById('corner').value),
|
|
||||||
globallength = parseFloat(document.getElementById('length').value),
|
|
||||||
globalsplice = parseInt(document.getElementById('splice').value),
|
|
||||||
globalfilterspeckle = parseInt(document.getElementById('filterspeckle').value),
|
|
||||||
globalcolorprecision = parseInt(document.getElementById('colorprecision').value),
|
|
||||||
globallayerdifference = parseInt(document.getElementById('layerdifference').value);
|
|
||||||
|
|
||||||
document.getElementById('none').addEventListener('click', function (e) {
|
|
||||||
mode = 'none';
|
|
||||||
restart();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.getElementById('polygon').addEventListener('click', function (e) {
|
|
||||||
mode = 'polygon';
|
|
||||||
restart();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.getElementById('spline').addEventListener('click', function (e) {
|
|
||||||
mode = 'spline';
|
|
||||||
restart();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.getElementById('clustering-binary').addEventListener('click', function (e) {
|
|
||||||
clustering_mode = 'binary';
|
|
||||||
restart();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.getElementById('clustering-color').addEventListener('click', function (e) {
|
|
||||||
clustering_mode = 'color';
|
|
||||||
restart();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.getElementById('filterspeckle').addEventListener('change', function (e) {
|
|
||||||
globalfilterspeckle = parseInt(this.value);
|
|
||||||
document.getElementById('filterspecklevalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('colorprecision').addEventListener('change', function (e) {
|
|
||||||
globalcolorprecision = parseInt(this.value);
|
|
||||||
document.getElementById('colorprecisionvalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('layerdifference').addEventListener('change', function (e) {
|
|
||||||
globallayerdifference = parseInt(this.value);
|
|
||||||
document.getElementById('layerdifferencevalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('corner').addEventListener('change', function (e) {
|
|
||||||
globalcorner = parseInt(this.value);
|
|
||||||
document.getElementById('cornervalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('length').addEventListener('change', function (e) {
|
|
||||||
globallength = parseFloat(this.value);
|
|
||||||
document.getElementById('lengthvalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('splice').addEventListener('change', function (e) {
|
|
||||||
globalsplice = parseInt(this.value);
|
|
||||||
document.getElementById('splicevalue').innerHTML = this.value;
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
function setSourceAndRestart(source) {
|
|
||||||
img.src = source instanceof File ? URL.createObjectURL(source) : source;
|
|
||||||
img.onload = function () {
|
|
||||||
svg.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
||||||
canvas.width = img.naturalWidth;
|
|
||||||
canvas.height = img.naturalHeight;
|
|
||||||
restart();
|
|
||||||
}
|
|
||||||
// Show display
|
|
||||||
canvas.style.display = 'block';
|
|
||||||
svg.style.display = 'block';
|
|
||||||
// Hide upload text
|
|
||||||
droptext.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function restart() {
|
|
||||||
document.getElementById('clustering-binary').classList.remove('selected');
|
|
||||||
document.getElementById('clustering-color').classList.remove('selected');
|
|
||||||
document.getElementById('clustering-' + clustering_mode).classList.add('selected');
|
|
||||||
Array.from(document.getElementsByClassName('clustering-color-options')).forEach((el) => {
|
|
||||||
el.style.display = clustering_mode == 'color' ? '' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('none').classList.remove('selected');
|
|
||||||
document.getElementById('polygon').classList.remove('selected');
|
|
||||||
document.getElementById('spline').classList.remove('selected');
|
|
||||||
document.getElementById(mode).classList.add('selected');
|
|
||||||
Array.from(document.getElementsByClassName('spline-options')).forEach((el) => {
|
|
||||||
el.style.display = mode == 'spline' ? '' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!img.src) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (svg.firstChild) {
|
|
||||||
svg.removeChild(svg.firstChild);
|
|
||||||
}
|
|
||||||
ctx.clearRect(0, 0, canvas.width,canvas.height);
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
let converter_params = JSON.stringify({
|
|
||||||
'canvas_id': canvas.id,
|
|
||||||
'svg_id': svg.id,
|
|
||||||
'mode': mode,
|
|
||||||
'clustering_mode': clustering_mode,
|
|
||||||
'corner_threshold': deg2rad(globalcorner),
|
|
||||||
'length_threshold': globallength,
|
|
||||||
'max_iterations': 10,
|
|
||||||
'splice_threshold': deg2rad(globalsplice),
|
|
||||||
'filter_speckle': globalfilterspeckle*globalfilterspeckle,
|
|
||||||
'color_precision': 8-globalcolorprecision,
|
|
||||||
'layer_difference': globallayerdifference,
|
|
||||||
});
|
|
||||||
if (runner) {
|
|
||||||
runner.stop();
|
|
||||||
}
|
|
||||||
runner = new ConverterRunner(converter_params);
|
|
||||||
progress.value = 0;
|
|
||||||
progressregion.style.display = 'block';
|
|
||||||
runner.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deg2rad(deg) {
|
|
||||||
return deg/180*3.141592654;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConverterRunner {
|
|
||||||
constructor (converter_params) {
|
|
||||||
this.converter =
|
|
||||||
clustering_mode == 'color' ?
|
|
||||||
ColorImageConverter.new_with_string(converter_params):
|
|
||||||
BinaryImageConverter.new_with_string(converter_params);
|
|
||||||
this.converter.init();
|
|
||||||
this.stopped = false;
|
|
||||||
if (clustering_mode == 'binary') {
|
|
||||||
svg.style.background = '#000';
|
|
||||||
canvas.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
svg.style.background = '';
|
|
||||||
canvas.style.display = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run () {
|
|
||||||
const This = this;
|
|
||||||
requestAnimationFrame(function tick () {
|
|
||||||
if (!This.stopped) {
|
|
||||||
if (!This.converter.tick()) {
|
|
||||||
progress.value = This.converter.progress();
|
|
||||||
if (progress.value >= 50) {
|
|
||||||
canvas.style.display = 'none';
|
|
||||||
}
|
|
||||||
if (progress.value >= progress.max) {
|
|
||||||
progressregion.style.display = 'none';
|
|
||||||
progress.value = 0;
|
|
||||||
}
|
|
||||||
requestAnimationFrame(tick);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download as SVG
|
||||||
|
document.getElementById('export').addEventListener('click', function (e) {
|
||||||
|
const blob = new Blob([new XMLSerializer().serializeToString(svg)], {type: 'octet/stream'}),
|
||||||
|
url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
this.href = url;
|
||||||
|
this.target = '_blank';
|
||||||
|
|
||||||
|
this.download = 'export-' + new Date().toISOString().slice(0, 19).replace(/:/g, '').replace('T', ' ') + '.svg';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to load a given config WITHOUT restarting
|
||||||
|
function loadConfig(config) {
|
||||||
|
mode = config.mode;
|
||||||
|
clustering_mode = config.clustering_mode;
|
||||||
|
|
||||||
|
globalcorner = config.corner_threshold;
|
||||||
|
document.getElementById('cornervalue').innerHTML = globalcorner;
|
||||||
|
document.getElementById('corner').value = globalcorner;
|
||||||
|
|
||||||
|
globallength = config.length_threshold;
|
||||||
|
document.getElementById('lengthvalue').innerHTML = globallength;
|
||||||
|
document.getElementById('length').value = globallength;
|
||||||
|
|
||||||
|
globalsplice = config.splice_threshold;
|
||||||
|
document.getElementById('splicevalue').innerHTML = globalsplice;
|
||||||
|
document.getElementById('splice').value = globalsplice;
|
||||||
|
|
||||||
|
globalfilterspeckle = config.filter_speckle;
|
||||||
|
document.getElementById('filterspecklevalue').innerHTML = globalfilterspeckle;
|
||||||
|
document.getElementById('filterspeckle').value = globalfilterspeckle;
|
||||||
|
|
||||||
|
globalcolorprecision = config.color_precision;
|
||||||
|
document.getElementById('colorprecisionvalue').innerHTML = globalcolorprecision;
|
||||||
|
document.getElementById('colorprecision').value = globalcolorprecision;
|
||||||
|
|
||||||
|
globallayerdifference = config.layer_difference;
|
||||||
|
document.getElementById('layerdifferencevalue').innerHTML = globallayerdifference;
|
||||||
|
document.getElementById('layerdifference').value = globallayerdifference;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
// Upload button
|
||||||
this.stopped = true;
|
var imageSelect = document.getElementById('imageSelect'),
|
||||||
|
imageInput = document.getElementById('imageInput');
|
||||||
|
imageSelect.addEventListener('click', function (e) {
|
||||||
|
imageInput.click();
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
imageInput.addEventListener('change', function (e) {
|
||||||
|
setSourceAndRestart(this.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag-n-Drop
|
||||||
|
var drop = document.getElementById('drop');
|
||||||
|
var droptext = document.getElementById('droptext');
|
||||||
|
drop.addEventListener('dragenter', function (e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
droptext.classList.add('hovering');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.addEventListener('dragleave', function (e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
droptext.classList.remove('hovering');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.addEventListener('dragover', function (e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
droptext.classList.add('hovering');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.addEventListener('drop', function (e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
droptext.classList.remove('hovering');
|
||||||
|
setSourceAndRestart(e.dataTransfer.files[0]);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get Input from UI controls
|
||||||
|
var globalcorner = parseInt(document.getElementById('corner').value),
|
||||||
|
globallength = parseFloat(document.getElementById('length').value),
|
||||||
|
globalsplice = parseInt(document.getElementById('splice').value),
|
||||||
|
globalfilterspeckle = parseInt(document.getElementById('filterspeckle').value),
|
||||||
|
globalcolorprecision = parseInt(document.getElementById('colorprecision').value),
|
||||||
|
globallayerdifference = parseInt(document.getElementById('layerdifference').value);
|
||||||
|
|
||||||
|
document.getElementById('none').addEventListener('click', function (e) {
|
||||||
|
mode = 'none';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('polygon').addEventListener('click', function (e) {
|
||||||
|
mode = 'polygon';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('spline').addEventListener('click', function (e) {
|
||||||
|
mode = 'spline';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('clustering-binary').addEventListener('click', function (e) {
|
||||||
|
clustering_mode = 'binary';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('clustering-color').addEventListener('click', function (e) {
|
||||||
|
clustering_mode = 'color';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('filterspeckle').addEventListener('change', function (e) {
|
||||||
|
globalfilterspeckle = parseInt(this.value);
|
||||||
|
document.getElementById('filterspecklevalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('colorprecision').addEventListener('change', function (e) {
|
||||||
|
globalcolorprecision = parseInt(this.value);
|
||||||
|
document.getElementById('colorprecisionvalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('layerdifference').addEventListener('change', function (e) {
|
||||||
|
globallayerdifference = parseInt(this.value);
|
||||||
|
document.getElementById('layerdifferencevalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('corner').addEventListener('change', function (e) {
|
||||||
|
globalcorner = parseInt(this.value);
|
||||||
|
document.getElementById('cornervalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('length').addEventListener('change', function (e) {
|
||||||
|
globallength = parseFloat(this.value);
|
||||||
|
document.getElementById('lengthvalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('splice').addEventListener('change', function (e) {
|
||||||
|
globalsplice = parseInt(this.value);
|
||||||
|
document.getElementById('splicevalue').innerHTML = this.value;
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setSourceAndRestart(source) {
|
||||||
|
img.src = source instanceof File ? URL.createObjectURL(source) : source;
|
||||||
|
img.onload = function () {
|
||||||
|
svg.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
||||||
|
canvas.width = img.naturalWidth;
|
||||||
|
canvas.height = img.naturalHeight;
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
// Show display
|
||||||
|
canvas.style.display = 'block';
|
||||||
|
svg.style.display = 'block';
|
||||||
|
// Hide upload text
|
||||||
|
droptext.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
function restart() {
|
||||||
|
document.getElementById('clustering-binary').classList.remove('selected');
|
||||||
|
document.getElementById('clustering-color').classList.remove('selected');
|
||||||
|
document.getElementById('clustering-' + clustering_mode).classList.add('selected');
|
||||||
|
Array.from(document.getElementsByClassName('clustering-color-options')).forEach((el) => {
|
||||||
|
el.style.display = clustering_mode == 'color' ? '' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('none').classList.remove('selected');
|
||||||
|
document.getElementById('polygon').classList.remove('selected');
|
||||||
|
document.getElementById('spline').classList.remove('selected');
|
||||||
|
document.getElementById(mode).classList.add('selected');
|
||||||
|
Array.from(document.getElementsByClassName('spline-options')).forEach((el) => {
|
||||||
|
el.style.display = mode == 'spline' ? '' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!img.src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (svg.firstChild) {
|
||||||
|
svg.removeChild(svg.firstChild);
|
||||||
|
}
|
||||||
|
ctx.clearRect(0, 0, canvas.width,canvas.height);
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
let converter_params = JSON.stringify({
|
||||||
|
'canvas_id': canvas.id,
|
||||||
|
'svg_id': svg.id,
|
||||||
|
'mode': mode,
|
||||||
|
'clustering_mode': clustering_mode,
|
||||||
|
'corner_threshold': deg2rad(globalcorner),
|
||||||
|
'length_threshold': globallength,
|
||||||
|
'max_iterations': 10,
|
||||||
|
'splice_threshold': deg2rad(globalsplice),
|
||||||
|
'filter_speckle': globalfilterspeckle*globalfilterspeckle,
|
||||||
|
'color_precision': 8-globalcolorprecision,
|
||||||
|
'layer_difference': globallayerdifference,
|
||||||
|
});
|
||||||
|
if (runner) {
|
||||||
|
runner.stop();
|
||||||
|
}
|
||||||
|
runner = new ConverterRunner(converter_params);
|
||||||
|
progress.value = 0;
|
||||||
|
progressregion.style.display = 'block';
|
||||||
|
runner.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deg2rad(deg) {
|
||||||
|
return deg/180*3.141592654;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConverterRunner {
|
||||||
|
constructor (converter_params) {
|
||||||
|
this.converter =
|
||||||
|
clustering_mode == 'color' ?
|
||||||
|
ColorImageConverter.new_with_string(converter_params):
|
||||||
|
BinaryImageConverter.new_with_string(converter_params);
|
||||||
|
this.converter.init();
|
||||||
|
this.stopped = false;
|
||||||
|
if (clustering_mode == 'binary') {
|
||||||
|
svg.style.background = '#000';
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
svg.style.background = '';
|
||||||
|
canvas.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run () {
|
||||||
|
const This = this;
|
||||||
|
requestAnimationFrame(function tick () {
|
||||||
|
if (!This.stopped) {
|
||||||
|
if (!This.converter.tick()) {
|
||||||
|
progress.value = This.converter.progress();
|
||||||
|
if (progress.value >= 50) {
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (progress.value >= progress.max) {
|
||||||
|
progressregion.style.display = 'none';
|
||||||
|
progress.value = 0;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop () {
|
||||||
|
this.stopped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user