Podnieś robienie zdjęć na Web na wyższy poziom z MediaTrackConstraints!

Spis treści

  1. Wprowadzenie
  2. Zastosowanie MediaTrackConstraints w robieniu zdjęć
  3. Kod przykładu na Github
  4. Działający przykład na Github Pages
  5. Podsumowanie

Wprowadzenie

Dzięki opisywanemu już tu przeze mnie Image Capture API można za pomocą JS robić zdjęcia za pośrednictwem kamery/aparatu:

Możesz jednak podnieść poziom robionych zdjęć za pomocą MediaTrackConstraints dla robienia zdjęć.

Przeczytaj ten artykuł by zobaczyć, jak regulować takie wartości jak jasność czy nasycenie zdjęcia. Dowiesz się też, jak włączyć lampę w aparacie telefonu by zawsze mieć dobre oświetlenie 🙂 Zapraszam do czytania!

Zastosowanie MediaTrackConstraints w robieniu zdjęć

Podstawową rzeczą jaka będzie nam potrzebna jest stream video:

const stream = await navigator.mediaDevices.getUserMedia({
  video: {
    facingMode: 'environment',
  },
});

Który podepniemy do znajdującego się na stronie elementu video. Ze streamu pobieżemy ściezkę wideo, której ustawienia będziemy modyfikować

const video = document.querySelector('#video');
video.srcObject = stream;
video.play();
const track = stream.getVideoTracks()[0];

Ścieżka pozwala na pobranie listy własności które możemy modyfikować metodą getCapabilities oraz aktualne wartości tych własności metodą getSettings:

const capabilities = track.getCapabilities();
const settings = track.getSettings();

Settings posłużą nam do wyświetlenia obecnej wartości, a capabilities do sprawdzenia, czy mamy możliwość sterowania własnością której potrzebujemy. Lista własności którymi będziemy chcieć sterować:

const constraintsToSupport = ['whiteBalanceMode', 'exposureMode', 'focusMode', 'exposureCompensation',
  'colorTemperature', 'iso', 'brightness', 'contrast', 'saturation', 'sharpness', 'focusDistance',
  'zoom', 'torch',
];

W pętli najpierw będziemy sprawdzać, czy dana funkcjonalność jest obecna w obiekcie capabilities, co oznaczać będzie że można nią sterować:

const capability = capabilities[constraint];
if (!capability) {
  return;
}

Kolejnym krokiem będzie zbudowanie UI potrzebnego do sterowania wartościami. Dzięki strukturze tego, co zwraca metoda getCapabilities możemy przyjąć 3 założenia:

  1. Jeżeli wartość jest listą, oznacza że przyjmuje konkretne zdefiniowane wartości. Wtedy chcemy przełączać je za pomocą selecta.
  2. Jeżeli wartość to boolean, oznacza to iż dana funkcjonalność może być tylko włączona bądź wyłączona. Wtedy do sterowania posłuży nam checkbox.
  3. Jeżeli wartość jest obiektem, wtedy zawiera wartość minimalną, maksymalną oraz krok. Do sterowania użyjemy suwaka który powstanie po utworzeniu inputu typu range.

Kod będzie prezentował się następująco:

if (Array.isArray(capability)) {
  management.innerHTML += `
    <div>
      <label for="${constraint}">${constraint}</label>
      <select id="${constraint}">
        ${capability.map(el => `<option value="${el}">${el}</option>`)}
      </select>
    </div>
  `;
  return;
}
if (typeof capability === 'boolean') {
  management.innerHTML += `
    <div>
      <label for="${constraint}">${constraint}</label>
      <input type="checkbox" id="${constraint}" ${settings[constraint] ? 'checked' : ''}>
    </div>
  `;
  return;
}
if (typeof capability === 'object') {
  management.innerHTML += `
    <div>
      <label for="${constraint}">${constraint}</label>
      <input type="range" id="${constraint}"
        min="${capability.min}" max="${capability.max}"
        step="${capability.step}" value="${settings[constraint]}">
    </div>
  `;
  return;
}

Dzięki takiemu podejściu wystarczy że do listy constraintsToSupport dodamy kolejny element którego będziemy potrzebować a odpowiedni UI zbuduje się sam.

UI do sterowania własnościami będzie różnił się w zależności od urządzeń na których uruchamiana jest aplikacja oraz samych kamer.
Przykładowo, na urządzeniach mobilnych będzie zawierać checkbox torch, umożliwiający włączenie lampy. Bardziej zaawansowane kamery będą dodatkowo mieć możliwość sterowania zoomem.

Przykładowe UI, dla prostej kamery zewnętrznej wyglądać będzie następująco:

Ostatnim krokiem jest utworzenie obiektu ImageCapture, który pozwoli na wykonanie zdjęcia oraz dodanie kodu odpowiedzialnego za pobranie elementu:

const capture = new ImageCapture(track);
  document.querySelector('#takePhoto').addEventListener('click', async () => {
  const blob = await capture.takePhoto();
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  const extension = blob.type.split('/')[1];
  a.download = `picture.${extension}`;
  a.click();
  URL.revokeObjectURL(url);
});

Jeśli chcesz zobaczyć pełnię możliwości tego przykładu, uruchom go na urządzeniu mobilnym – tam będziesz mieć możliwość uruchomienia lampy błyskowej bądź zarządzania zoomem, co wygląda naprawdę efektownie 🙂

Kod przykładu na Github

Pełny kod przykładu dostępny jest pod adresem:

https://github.com/radek-anuszewski/mediatrackconstraints

Działający przykład na Github Pages

Klikalna wersja aplikacji dostępna jest pod adresem:

https://radek-anuszewski.github.io/mediatrackconstraints/

Podsumowanie

Dzięki ImageCapture robienie zdjęć na Web stało się prostsze i efektywniejsze – a dzięki zarządzaniu własnościami MediaTrackConstraints możemy je jeszcze dodatkowo ulepszyć, dając użytkownikowi możliwość zarządzania parametrami dostępnymi wydawałoby się tylko z poziomu natywnej aplikacji obsługującej aparat w urządzeniu.