Sprawdź jak dzięki ImageCapture zrobisz zdjęcie prościej w swojej aplikacji

Spis treści

  1. Wprowadzenie
  2. Kod bazowy dla porównania ImageCapture vs canvas
  3. Robienie zdjęcia
    1. Stary sposób z canvas
    2. Nowy sposób z ImageCapture
  4. Wsparcie ImageCapture
  5. Kod przykładu na Github
  6. Klikalny przykład na Github Pages
  7. Podsumowanie

Wprowadzenie

We wszystkich aplikacjach WWW w których pracowałem do tej pory robienie zdjęcia polegało na podpięciu się do wideo z kamery i przechwyceniu pojedynczej klatki strumienia. Klatka ta była potem rysowana na elemencie <canvas> a następnie zapisywana jako blob.

To podejście jest hakiem, wymaga nadmiarowych elementów. Niedawno natknąłem się na dedykowane przeglądarkowe API ImageCapture, opisane m.in. na Mozilli. Pozwala ono zrobić zdjęcie w znacznie prostszy sposób, bez elementów pośrednich i obejść.

Sprawdźmy więc czym różni się nowe podejście od starego.

Kod bazowy dla porównania ImageCapture vs canvas

Stwórzmy kod który pozwoli porównać oba sposoby – zawierał on będzie możliwość wyboru kamery przód/tył oraz możliwość włączenia lampy. UI będzie się prezentował następująco:

Funkcja obsługująca przyciski wygląda tak:

const selectCamera = async () => {
  stopCamera();
  const camera = cameras.find(el => el.checked).value;
  const facingMode = camera === 'front'? 'user' : 'environment';

  const stream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode },
  });
  [track] = stream.getVideoTracks();
  preview.srcObject = stream;
  const capabilities = track.getCapabilities();
  if (capabilities.torch) {
    const torch = light.checked;
    await track.applyConstraints({
      advanced: [{ torch }]
    })
  }
  preview.play();
};

Na początku sprawdzamy, czy wybrana została przednia czy tylna kamera. Dla kamery przedniej parametr facingMode należy ustawić na 'user', dla tylnej na 'environment'.

Kolejnym krokiem jest pobranie ścieżki, która zostanie przekazana do ImageCapture.

Jeżeli użytkownik wybrał włączoną lampę, sprawdzamy czy w danej kamerze jest możliwość uruchomienia lampy. Jeżeli próbowalibyśmy uruchomić lampę bez jej wsparcia, dostalibyśmy błąd.

Samo narysowanie zrobionego zdjęcia wygląda następująco:

const drawPhoto = (blob, photo) => {
  if (photo.src) {
    URL.revokeObjectURL(photo.src);
  }
  photo.src = URL.createObjectURL(blob);
}

Pamiętaj by przykład znajdujący się w dalszej części obejrzeć również na telefonie – zobaczysz wtedy, jak działa zmiana kamery przód tył oraz lampa błyskowa

Robienie zdjęcia

Stary sposób z canvas

Oto kod starego sposobu robienia zdjęcia:

const takePhotoOld = () => {
  const canvas = document.createElement('canvas');
  const { width, height } = track.getSettings();
  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext('2d');
  context.drawImage(preview, 0, 0, canvas.width, canvas.height);
  canvas.toBlob(blob => drawPhoto(blob, photoOld));
}

Musimy stworzyć pośredni element <canvas>, na którym zarysowana zostanie przechwycona klatka.

Jest to rozwiązanie bardzo popularne, jednak jego działanie nie polega na zrobieniu zdjęcia a na wychwyceniu klatki ze strumienia.

Nowy sposób z ImageCapture

Nowy sposób jest znacznie bardziej zwięzły:

const takePhotoNew = async () => {
  const imageCapture = new ImageCapture(track);
  const blob = await imageCapture.takePhoto();
  drawPhoto(blob, photoNew);
};

Polega na przekazaniu pobranej wcześniej ścieżki do ImageCapture i wywołania funkcji takePhoto.

Jeżeli na telefonie włączymy lampę błyskową, zauważymy flash tej lampy przy robieniu zdjęcia – ponieważ dochodzi tutaj do rzeczywistego zrobienia zdjęcia, a nie tylko pobrania klatki ze streama.

Wsparcie ImageCapture

Wsparcie jest pełne we wszystkich przeglądarkach opartych na Chromium, co daje nam ok 70% udziału w rynku. Okazuje się jednak, że Mozilla również pracuje nad tą funkcjonalnością – aktualnie za flagą dostępna jest metoda grabFrame, pobierająca pojedynczą klatkę ze streama. Dlatego jest szansa na to, że również Firefox będzie wspierał to API – a póki co można dla starszych przeglądarek dostarczać sposób z <canvas> jako fallback.

Kod przykładu na Github

Pełny kod przykładu znajdziesz pod adresem:

https://github.com/radek-anuszewski/image-capture-demo

Klikalny przykład na Github Pages

Działający przykład znajdziesz poniżej. Pamiętaj, by uruchomić go również w przeglądarce mobilnej by móc zobaczyć w akcji zmianę kamery przód/tył i lampę błyskową:

https://radek-anuszewski.github.io/image-capture-demo/

Podsumowanie

ImageCapture umożliwia zrobienie zdjęcia w sposób prostszy i bez elementów pośrednich. To, że inni dostawcy też zaczynają implementować to API daje szansę na to, że haki z <canvas> odejdą do lamusa – nie spodziewałbym się jednak, że stanie się to szybko.