<!DOCTYPE html>
<html>
<head>
  <script src="face-api.js"></script>
  <script src="commons.js"></script>
  <link rel="stylesheet" href="styles.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
  <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
  <div id="navbar"></div>
  <div class="center-content page-container">
    <div class="progress" id="loader">
      <div class="indeterminate"></div>
    </div>
    <div style="position: relative" class="margin">
      <img id="inputImg" src="" style="max-width: 800px;" />
      <canvas id="overlay" />
    </div>
    <div id="facesContainer"></div>
    <div class="row side-by-side">
      <div id="selectList"></div>
      <div class="row">
        <label for="imgUrlInput">Get image from URL:</label>
        <input id="imgUrlInput" type="text" class="bold">
      </div>
      <button
        class="waves-effect waves-light btn"
        onclick="loadImageFromUrl()"
      >
        Ok
      </button>
    </div>
    <div class="row side-by-side">
      <div class="row">
        <label for="minConfidence">Min Confidence:</label>
        <input disabled value="0.7" id="minConfidence" type="text" class="bold">
      </div>
      <button
        class="waves-effect waves-light btn"
        onclick="onDecreaseMinConfidence()"
      >
        <i class="material-icons left">-</i>
      </button>
      <button
        class="waves-effect waves-light btn"
        onclick="onIncreaseMinConfidence()"
      >
        <i class="material-icons left">+</i>
      </button>
    </div>
    <div class="row">
      <p>
        <input type="checkbox" id="drawLinesCheckbox" onchange="onChangeUseMtcnn(event)" />
        <label for="drawLinesCheckbox">Use Mtcnn</label>
      </p>
    </div>
  </div>

  <script>
    let minConfidence = 0.7
    let useMtcnn = false

    function onIncreaseMinConfidence() {
      minConfidence = Math.min(faceapi.round(minConfidence + 0.1), 1.0)
      $('#minConfidence').val(minConfidence)
      updateResults()
    }

    function onDecreaseMinConfidence() {
      minConfidence = Math.max(faceapi.round(minConfidence - 0.1), 0.1)
      $('#minConfidence').val(minConfidence)
      updateResults()
    }

    function onChangeUseMtcnn(e) {
      useMtcnn = $(e.target).prop('checked')
      updateResults()
    }

    async function loadImageFromUrl(url) {
      const img = await requestExternalImage($('#imgUrlInput').val())
      $('#inputImg').get(0).src = img.src
      updateResults()
    }

    async function locateAndAlignFacesWithMtcnn(inputImgEl) {
      const input = await faceapi.toNetInput(
        inputImgEl,
        // dispose input manually
        false,
        // keep canvases (required for mtcnn)
        true
      )

      const results = await faceapi.mtcnn(input, { minFaceSize: 100 })

      const unalignedFaceImages = await faceapi.extractFaces(input.inputs[0], results.map(res => res.faceDetection))

      const alignedFaceBoxes = results
        .filter(res => res.faceDetection.score > minConfidence)
        .map(res => res.faceLandmarks.align())

      const alignedFaceImages = await faceapi.extractFaces(input.inputs[0], alignedFaceBoxes)

      // free memory for input tensors
      input.dispose()

      return {
        unalignedFaceImages,
        alignedFaceImages
      }
    }

    async function locateAndAlignFacesWithSSD(inputImgEl) {
      const input = await faceapi.toNetInput(inputImgEl)

      const locations = await faceapi.locateFaces(input, minConfidence)

      const unalignedFaceImages = await faceapi.extractFaces(input.inputs[0], locations)

      // detect landmarks and get the aligned face image bounding boxes
      const alignedFaceBoxes = await Promise.all(unalignedFaceImages.map(
        async (faceCanvas, i) => {
          const faceLandmarks = await faceapi.detectLandmarks(faceCanvas)
          return faceLandmarks.align(locations[i])
        }
      ))
      const alignedFaceImages = await faceapi.extractFaces(input.inputs[0], alignedFaceBoxes)

      // free memory for input tensors
      input.dispose()

      return {
        unalignedFaceImages,
        alignedFaceImages
      }
    }

    async function updateResults() {
      const inputImgEl = $('#inputImg').get(0)
      const { width, height } = inputImgEl
      const canvas = $('#overlay').get(0)
      canvas.width = width
      canvas.height = height

      const {
        unalignedFaceImages,
        alignedFaceImages
      } = useMtcnn
        ? await locateAndAlignFacesWithMtcnn(inputImgEl)
        : await locateAndAlignFacesWithSSD(inputImgEl)

      $('#facesContainer').empty()
      unalignedFaceImages.forEach(async (faceCanvas, i) => {
        $('#facesContainer').append(faceCanvas)
        $('#facesContainer').append(alignedFaceImages[i])
      })
    }

    async function onSelectionChanged(uri) {
      const imgBuf = await fetchImage(uri)
      $(`#inputImg`).get(0).src = (await faceapi.bufferToImage(imgBuf)).src
      updateResults()
    }

    async function run() {
      await faceapi.loadFaceDetectionModel('/')
      await faceapi.loadFaceLandmarkModel('/')
      await faceapi.loadMtcnnModel('/')
      $('#loader').hide()
      onSelectionChanged($('#selectList select').val())
    }

    $(document).ready(function() {
      renderNavBar('#navbar', 'face_alignment')
      renderImageSelectList(
        '#selectList',
        async (uri) => {
          await onSelectionChanged(uri)
        },
        'bbt1.jpg'
      )
      run()
    })
  </script>
</body>
</html>