text: import { BinaryImageConverter, ColorImageConverter } from 'vtracer';
  
  let runner;
  const canvas = document.getElementById('frame');
  const ctx = canvas.getContext('2d');
  const svg = document.getElementById('svg');
  const img = new Image();
  const progress = document.getElementById('progressbar');
  const progressregion = document.getElementById('progressregion');
  let mode = 'spline', clustering_mode = 'color';
  
  // Hide canas and svg on load
  canvas.style.display = 'none';
  svg.style.display = 'none';
  
  // Paste from clipboard
  document.addEventListener('paste', function (e) {
  	if (e.clipboardData) {
  		var items = e.clipboardData.items;
  		if (!items) return;
  
  		//access data directly
  		for (var i = 0; i < items.length; i++) {
  			if (items[i].type.indexOf("image") !== -1) {
  				//image
  				var blob = items[i].getAsFile();
  				var URLObj = window.URL || window.webkitURL;
  				var source = URLObj.createObjectURL(blob);
  				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 = '';
          }
          canvas.style.opacity = '';
      }
  
      run () {
          const This = this;
          setTimeout(function tick () {
              if (!This.stopped) {
                  let done = false;
                  const startTick = performance.now();
                  while (!(done = This.converter.tick()) &&
                      performance.now() - startTick < 25) {
                  }
                  progress.value = This.converter.progress();
                  if (progress.value >= 50) {
                      canvas.style.display = 'none';
                  } else {
                      canvas.style.opacity = (50 - progress.value) / 25;
                  }
                  if (progress.value >= progress.max) {
                      progressregion.style.display = 'none';
                      progress.value = 0;
                  }
                  if (!done) {
                      setTimeout(tick, 1);
                  }
              }
          }, 1);
      }
  
      stop () {
          this.stopped = true;
      }
  }